Compare commits

..
Author SHA1 Message Date
james.maesandCodex 7c967d0c88 ci: adopt central QQQ orb 0.7.4
Co-Authored-By: Codex <noreply@openai.com>
2026-10-20 19:27:12 -05:00
james.maes 5c52abdfe3 Merge pull request #14 from QRun-IO/feature/release-rc-automation
ci: hotfix/* pushes no longer publish
2026-10-13 15:01:50 -05:00
james.maesandClaude Opus 5.5 6799f645e9 ci: keep apt unattended (configure-package-install pre-step)
Every job hung in "Install Java 21" on the needrestart dialog until the
10m no-output timeout. Adds the standard's configure-package-install
command (QRUN-QBIT-REPO-STANDARD C1) as a pre-step to each orb job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:59:48 -05:00
james.maesandClaude Opus 5.5 a16c153e7e ci: hotfix/* pushes no longer publish
Removes publish_hotfix_release: a hotfix/* branch push published a GA
release to Maven Central with no tag or review (QRun-IO/qbit-cdn#12,
QRun-IO/qqq#1121). hotfix/* branches now run test_only; a hotfix
release publishes from a reviewed vX.Y.Z tag through publish_release.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 14:38:11 -05:00
james.maes 67b50143ad Merge pull request #5 from QRun-IO/dependabot/maven/maven-099704d0fe
Bump org.assertj:assertj-core from 3.27.3 to 3.27.7 in the maven group across 1 directory
2026-10-01 20:58:39 -05:00
dependabot[bot] e4d8e06719 Bump org.assertj:assertj-core in the maven group across 1 directory
Bumps the maven group with 1 update in the / directory: [org.assertj:assertj-core](https://github.com/assertj/assertj).


Updates `org.assertj:assertj-core` from 3.27.3 to 3.27.7
- [Release notes](https://github.com/assertj/assertj/releases)
- [Commits](https://github.com/assertj/assertj/compare/assertj-build-3.27.3...assertj-build-3.27.7)

---
updated-dependencies:
- dependency-name: org.assertj:assertj-core
  dependency-version: 3.27.7
  dependency-type: direct:development
  dependency-group: maven
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 21:56:59 +00:00
119 changed files with 1358 additions and 5136 deletions

No files matched your search

+3 -5
View File
@@ -1,16 +1,14 @@
version: 2.1
setup: true
orbs:
qqq-orb: kingsrook/qqq-orb@0.8
qqq-orb: kingsrook/qqq-orb@0.7.4
workflows:
setup:
jobs:
- qqq-orb/generate_pipeline:
orb_version: '0.8'
orb_version: 0.7.4
filters:
branches:
ignore: automation/qbit-rc-pending
tags:
only:
- /^v\d+\.\d+\.\d+$/
- /publish.*/
only: /v.*/
-7
View File
@@ -1,7 +0,0 @@
name: build
on:
push:
jobs:
maven:
uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/maven.yaml@v1
secrets: inherit
-23
View File
@@ -1,23 +0,0 @@
name: CodeQL
'on':
push:
branches:
- main
- develop
- release/**
- hotfix/**
pull_request: {}
schedule:
- cron: 41 16 * * 0
workflow_dispatch: {}
permissions:
contents: read
actions: read
security-events: write
packages: read
concurrency:
group: codeql-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
analyze:
uses: QRun-IO/qqq-orb/.github/workflows/qbit-codeql.yml@c288a8864fe9054f68410e1d78f5863aea4c4d22
-74
View File
@@ -1,74 +0,0 @@
name: Release candidate
'on':
workflow_dispatch:
inputs:
dry_run:
description: Plan a cut without changing release state
type: boolean
default: false
finalize:
description: Retry finalization of an existing cut; do not cut another RC
type: boolean
default: false
recover:
description: Recover the same published RC after a deployment timeout; never
reupload
type: boolean
default: false
circle_build:
description: Original Circle publisher build number (recovery only)
type: string
default: ''
merge_sha:
description: Existing cut merge_sha (finalize retry only)
type: string
default: ''
bump_sha:
description: Existing cut bump_sha (finalize retry only)
type: string
default: ''
branch:
description: Existing cut branch (finalize retry only)
type: string
default: ''
version:
description: Existing cut version (finalize retry only)
type: string
default: ''
pull_request:
types:
- closed
branches:
- develop
repository_dispatch:
types:
- qbit-rc-published
permissions:
contents: write
actions: read
checks: read
jobs:
release-rc:
if: github.event_name == 'workflow_dispatch' || github.event_name == 'repository_dispatch'
|| (github.event_name == 'pull_request' && github.event.pull_request.merged
== true && github.event.pull_request.base.ref == 'develop' && contains(github.event.pull_request.labels.*.name,
'release:rc'))
uses: QRun-IO/qqq-orb/.github/workflows/qbit-release-rc.yml@cc30f599e63c8e41629c99d23395bac1adb8bc44
with:
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run || false
}}
finalize: ${{ github.event_name == 'repository_dispatch' || (github.event_name
== 'workflow_dispatch' && inputs.finalize) }}
recover: ${{ github.event_name == 'workflow_dispatch' && inputs.recover || false
}}
circle_build: ${{ github.event_name == 'workflow_dispatch' && inputs.circle_build
|| '' }}
merge_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.merge_sha
|| inputs.merge_sha || '' }}
bump_sha: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.bump_sha
|| inputs.bump_sha || '' }}
branch: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.branch
|| inputs.branch || '' }}
version: ${{ github.event_name == 'repository_dispatch' && github.event.client_payload.version
|| inputs.version || '' }}
secrets: inherit
+4 -5
View File
@@ -1,17 +1,16 @@
profile: qbit
orb_version: '0.8'
orb_version: 0.7.4
default_branch: main
java_version: '21'
browsers: true
api_checks: false
docs_only: false
release_on_main: false
release_tag_policy: stable
test_branch_policy: feature-no-hotfix
release_tag_policy: v-prefix
test_branch_policy: feature
publish:
snapshot: true
release_candidate: true
release: true
hotfix: true
feature: true
asciidoc: false
release_completion: false
+5 -6
View File
@@ -14,11 +14,10 @@ vault:
Key facts from the review (see dossier for detail):
- develop carries unreleased 2026 feature work plus everything on main, and builds on
qqq 4.0.0 via parent `com.kingsrook:qbit-build-parent:2.0.0` (`-Pqqq-snapshot`
checks qqq 4.1.0-SNAPSHOT); main is the released line (0.4.0) on qqq 0.35.0 via
parent 1.5.1.
- origin/develop is 32 commits ahead of main (unreleased 2026 feature work, pinned to
a throwaway `0.40.0-join-enhancements-*` qqq build); main is the released line on
qqq 0.35.0 via parent `com.kingsrook:qbit-build-parent:1.5.1`.
- README and CHANGELOG.md do not describe this codebase (template rot) — trust the
source and the dossier instead.
- Current first-party license declarations use Apache-2.0 consistently across
LICENSE/NOTICE, the pom, source headers, Checkstyle template and README.
- Licensing is inconsistent (LICENSE/NOTICE Apache-2.0 vs pom + file headers AGPL-3.0
vs README "Proprietary") — pending reconciliation.
+2 -2
View File
@@ -24,7 +24,7 @@ This QBit provides a declarative workflow engine. Define states and transitions,
### Prerequisites
- QQQ application (v4.0.0+)
- QQQ application (v0.35.0+)
- Database backend configured
### Installation
@@ -147,4 +147,4 @@ Stable and production-ready.
## License
Apache-2.0 - See [LICENSE](LICENSE) and [NOTICE](NOTICE).
Proprietary - QRun.IO
+10 -9
View File
@@ -6,17 +6,18 @@
~ contact@kingsrook.com
~ https://github.com/Kingsrook/
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~ 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.
~
~ https://www.apache.org/licenses/LICENSE-2.0
~ 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.
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~ 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/>.
-->
<!DOCTYPE module PUBLIC
+10 -9
View File
@@ -5,15 +5,16 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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/>.
*/
+16 -39
View File
@@ -6,30 +6,30 @@
~ contact@kingsrook.com
~ https://github.com/Kingsrook/
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~ 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.
~
~ https://www.apache.org/licenses/LICENSE-2.0
~ 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.
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
~ 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/>.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<properties>
<qqq.version>4.1.0-RC.26</qqq.version> <!-- feeds qbit-build-parent's qqq-bom-pom import -->
<revision>0.114.0-SNAPSHOT</revision>
<revision>0.4.0</revision>
</properties>
<parent>
<groupId>com.kingsrook</groupId>
<artifactId>qbit-build-parent</artifactId>
<version>2.2.1</version>
<version>1.5.1</version>
</parent>
<groupId>com.kingsrook.qbits</groupId>
@@ -43,8 +43,8 @@
<licenses>
<license>
<name>Apache License, Version 2.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url>
<name>GNU Affero General Public License v3.0</name>
<url>https://www.gnu.org/licenses/agpl-3.0.html</url>
<distribution>repo</distribution>
</license>
</licenses>
@@ -87,7 +87,7 @@
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-material-dashboard</artifactId>
<version>0.42.0-RC.2</version>
<version>0.27.0-RC.1</version>
</dependency>
<dependency>
@@ -103,27 +103,4 @@
</dependency>
</dependencies>
<profiles>
<!-- Opt-in check against the next qqq line: mvn -B verify -Pqqq-snapshot -->
<!-- (override the version with -Dqqq.snapshot.version=...). Default builds -->
<!-- take the qqq version only from qbit-build-parent (ADR-0007). -->
<profile>
<id>qqq-snapshot</id>
<properties>
<qqq.snapshot.version>4.1.0-RC.26</qqq.snapshot.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-bom-pom</artifactId>
<version>${qqq.snapshot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</profile>
</profiles>
</project>
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows;
@@ -172,9 +173,8 @@ public class WorkflowsQBitProducer implements QBitMetaDataProducer<WorkflowsQBit
tableTriggerTable.withField(new QFieldMetaData("workflowId", QFieldType.INTEGER)
.withBackendName("workflow_id")
.withPossibleValueSourceName(Workflow.TABLE_NAME)
.withPossibleValueSourceFilter(new QQueryFilter()
.withCriteria(new QFilterCriteria("workflowTypeName", QCriteriaOperator.EQUALS, RecordWorkflowsDefinition.WORKFLOW_TYPE))
.withCriteria(new QFilterCriteria("tableName", QCriteriaOperator.EQUALS, "${input.tableName}"))));
.withPossibleValueSourceFilter(new QQueryFilter(
new QFilterCriteria("tableName", QCriteriaOperator.EQUALS, "${input.tableName}"))));
//////////////////////////////////////////////////////////////////////////////
// place workflowId field after scriptId in whatever section scriptId is in //
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.definition;
@@ -27,7 +28,7 @@ package com.kingsrook.qbits.workflows.definition;
** ONE is a regular statement, that just flows to its next.
** CONTAINER is itself a no-op, but it wraps (for UI reasons) a sequence
** TWO is for an if/else
** VARIABLE is for a variable number - 1 or more
** Unclear exactly how we'd do variable number of outbounds (e.g., a switch)
***************************************************************************/
public enum OutboundLinkMode
{
@@ -35,5 +36,4 @@ public enum OutboundLinkMode
ONE,
CONTAINER,
TWO,
VARIABLE
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.definition;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.definition;
@@ -25,7 +26,6 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.gson.reflect.TypeToken;
import com.kingsrook.qbits.workflows.execution.WorkflowStepExecutorInterface;
import com.kingsrook.qqq.backend.core.actions.customizers.QCodeLoader;
@@ -56,10 +56,8 @@ public class WorkflowStepType implements Serializable
private OutboundLinkMode outboundLinkMode;
private List<OutboundLinkOption> outboundLinkOptions;
private Set<String> allowedDescendantStepTypes;
private ArrayList<QFieldMetaData> inputFields = new ArrayList<>();
private ArrayList<String> inputWidgetNames = new ArrayList<>();
private ArrayList<QFieldMetaData> inputFields;
private ArrayList<String> inputWidgetNames;
@@ -85,17 +83,6 @@ public class WorkflowStepType implements Serializable
/***************************************************************************
* for a variable-output type step, it must dynamically define its outbound
* links
***************************************************************************/
public List<OutboundLinkOption> getDynamicOutboundLinkOptions(Integer workflowId, Map<String, Serializable> inputValues) throws QException
{
return (null);
}
/***************************************************************************
**
***************************************************************************/
@@ -422,7 +409,6 @@ public class WorkflowStepType implements Serializable
}
/*******************************************************************************
* Getter for validator
* @see #withValidator(QCodeReference)
@@ -459,42 +445,4 @@ public class WorkflowStepType implements Serializable
}
/*******************************************************************************
* Getter for allowedDescendantStepTypes
* @see #withAllowedDescendantStepTypes(Set)
*******************************************************************************/
public Set<String> getAllowedDescendantStepTypes()
{
return (this.allowedDescendantStepTypes);
}
/*******************************************************************************
* Setter for allowedDescendantStepTypes
* @see #withAllowedDescendantStepTypes(Set)
*******************************************************************************/
public void setAllowedDescendantStepTypes(Set<String> allowedDescendantStepTypes)
{
this.allowedDescendantStepTypes = allowedDescendantStepTypes;
}
/*******************************************************************************
* Fluent setter for allowedDescendantStepTypes
*
* @param allowedDescendantStepTypes
* to limit what step types can be placed inside this step type within a graph.
* for example,
* @return this
*******************************************************************************/
public WorkflowStepType withAllowedDescendantStepTypes(Set<String> allowedDescendantStepTypes)
{
this.allowedDescendantStepTypes = allowedDescendantStepTypes;
return (this);
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.definition;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.definition;
@@ -24,19 +25,16 @@ package com.kingsrook.qbits.workflows.definition;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.gson.reflect.TypeToken;
import com.kingsrook.qbits.workflows.execution.WorkflowTypeExecutorInterface;
import com.kingsrook.qbits.workflows.execution.WorkflowTypeTesterInterface;
import com.kingsrook.qbits.workflows.execution.WorkflowTypeValidatorInterface;
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
@@ -52,11 +50,9 @@ public class WorkflowType implements Serializable
private String label;
private String description;
private QCodeReference executor;
private QCodeReference validator;
private QCodeReference tester;
protected Map<String, List<QHelpContent>> helpContent;
private ArrayList<WorkflowStepTypeCategory> stepTypeCategories;
private ArrayList<WorkflowStepTypeCategory> stepTypeCategories;
@@ -85,11 +81,6 @@ public class WorkflowType implements Serializable
qInstanceValidator.validateSimpleCodeReference("WorkflowType [" + name + "]: executor ", executor, WorkflowTypeExecutorInterface.class);
}
if(validator != null)
{
qInstanceValidator.validateSimpleCodeReference("WorkflowType [" + name + "]: validator ", validator, WorkflowTypeValidatorInterface.class);
}
if(tester != null)
{
qInstanceValidator.validateSimpleCodeReference("WorkflowType [" + name + "]: tester ", tester, WorkflowTypeTesterInterface.class);
@@ -275,8 +266,6 @@ public class WorkflowType implements Serializable
return Collections.emptyMap();
}
/*******************************************************************************
* Getter for tester
* @see #withTester(QCodeReference)
@@ -314,92 +303,4 @@ public class WorkflowType implements Serializable
}
/*******************************************************************************
** Getter for helpContent
*******************************************************************************/
public Map<String, List<QHelpContent>> getHelpContent()
{
return (this.helpContent);
}
/*******************************************************************************
** Setter for helpContent
*******************************************************************************/
public void setHelpContent(Map<String, List<QHelpContent>> helpContent)
{
this.helpContent = helpContent;
}
/*******************************************************************************
** Fluent setter for helpContent
*******************************************************************************/
public WorkflowType withHelpContent(Map<String, List<QHelpContent>> helpContent)
{
this.helpContent = helpContent;
return (this);
}
/*******************************************************************************
** Fluent setter for helpContent
*******************************************************************************/
public WorkflowType withHelpContent(String key, QHelpContent helpContent)
{
if(this.helpContent == null)
{
this.helpContent = new HashMap<>();
}
if(!this.helpContent.containsKey(key))
{
this.helpContent.put(key, new ArrayList<>());
}
this.helpContent.get(key).add(helpContent);
return (this);
}
/*******************************************************************************
* Getter for validator
* @see #withValidator(QCodeReference)
*******************************************************************************/
public QCodeReference getValidator()
{
return (this.validator);
}
/*******************************************************************************
* Setter for validator
* @see #withValidator(QCodeReference)
*******************************************************************************/
public void setValidator(QCodeReference validator)
{
this.validator = validator;
}
/*******************************************************************************
* Fluent setter for validator
*
* @param validator reference to implementation of
* {@link com.kingsrook.qbits.workflows.execution.WorkflowTypeValidatorInterface}
*
* @return this
*******************************************************************************/
public WorkflowType withValidator(QCodeReference validator)
{
this.validator = validator;
return (this);
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.definition;
@@ -202,8 +203,6 @@ public class WorkflowsRegistry implements QSupplementalInstanceMetaData, QHelpCo
/***************************************************************************
* plug in to the QQQ Help Content system, to allow help content to set
* descriptions and labels for workflow steps.
*
* Note: helpContent can come in here as null, if it has been, e.g., deleted!
***************************************************************************/
@Override
public void acceptHelpContent(QInstance qInstance, QHelpContent helpContent, Map<String, String> nameValuePairs)
@@ -216,18 +215,11 @@ public class WorkflowsRegistry implements QSupplementalInstanceMetaData, QHelpCo
{
if("description".equals(nameValuePairs.get("slot")))
{
workflowStepType.setDescription(helpContent == null ? null : helpContent.getContentAsHtml());
workflowStepType.setDescription(helpContent.getContentAsHtml());
}
else if("label".equals(nameValuePairs.get("slot")))
{
if(helpContent != null)
{
workflowStepType.setLabel(helpContent.getContent());
}
else
{
LOG.info("Not removing workflow step type label after deleted help content (original label is not known...", logPair("name", workflowStepTypeName));
}
workflowStepType.setLabel(helpContent.getContent());
}
else if(nameValuePairs.containsKey("field"))
{
@@ -237,10 +229,9 @@ public class WorkflowsRegistry implements QSupplementalInstanceMetaData, QHelpCo
.withType(QFieldType.STRING)
.withLabel("Step Description")
.withName("qswdDescription")
.withHelpContent(helpContent)
.withGridColumns(12);
newField.setHelpContents(helpContent == null ? null : ListBuilder.of(helpContent));
List<QFieldMetaData> existingFields = workflowStepType.getInputFields();
existingFields.add(newField);
workflowStepType.setInputFields(existingFields);
@@ -251,24 +242,13 @@ public class WorkflowsRegistry implements QSupplementalInstanceMetaData, QHelpCo
{
if(nameValuePairs.get("field").equals(fieldMetaData.getName()))
{
fieldMetaData.setHelpContents(helpContent == null ? null : ListBuilder.of(helpContent));
fieldMetaData.setHelpContents(ListBuilder.of(helpContent));
}
}
}
}
}
}
else if(nameValuePairs.containsKey("workflow") && nameValuePairs.containsKey("field"))
{
if(workflowTypes != null)
{
for(String workflowTypeName : workflowTypes.keySet())
{
String key = "workflow:" + nameValuePairs.get("workflow") + ";field:" + nameValuePairs.get("field");
workflowTypes.get(workflowTypeName).withHelpContent(key, helpContent);
}
}
}
}
}
@@ -1,40 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.workflows.execution;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import com.kingsrook.qbits.workflows.model.WorkflowLink;
import com.kingsrook.qbits.workflows.model.WorkflowRunLogStep;
import com.kingsrook.qbits.workflows.model.WorkflowStep;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
/***************************************************************************
* state data carried throughout the {@link WorkflowExecutor} - which is
* useful to "leak out" in a managed way, e.g., for step types that do their
* own more sophisticated kind of execution, e.g., {@link WorkflowMultiForkingStepExecutorInterface}
***************************************************************************/
record ExecutionPayload(Map<Integer, WorkflowStep> stepMap, ListingHash<Integer, WorkflowLink> linkMap, WorkflowTypeExecutorInterface workflowTypeExecutor, AtomicInteger seqNo, List<WorkflowRunLogStep> logStepList)
{
}
@@ -1,166 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.workflows.execution;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import com.kingsrook.qbits.workflows.model.WorkflowLink;
import com.kingsrook.qbits.workflows.model.WorkflowStep;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
/*******************************************************************************
* given a graph, find the first "join point" where multiple forks come together.
* Credit: https://chatgpt.com/share/68000e8f-879c-8011-bb33-0564eb77a1f2
*******************************************************************************/
public class FindFirstJoinPoint
{
/***************************************************************************
**
***************************************************************************/
public static Integer executeForFromStepNo(Integer fromStepNo, Map<Integer, WorkflowStep> stepMap, ListingHash<Integer, WorkflowLink> linksMapByFromStepNo)
{
List<Integer> startLinks = CollectionUtils.nonNullList(linksMapByFromStepNo.get(fromStepNo)).stream().map(ws -> ws.getToStepNo()).toList();
return execute(startLinks, stepMap, linksMapByFromStepNo);
}
/***************************************************************************
**
***************************************************************************/
public static Integer executeForLinks(Collection<WorkflowLink> startLinks, Map<Integer, WorkflowStep> stepMap, ListingHash<Integer, WorkflowLink> linksMapByFromStepNo)
{
return execute(startLinks.stream().map(ws -> ws.getToStepNo()).toList(), stepMap, linksMapByFromStepNo);
}
/***************************************************************************
**
***************************************************************************/
@SuppressWarnings("Java8MapApi")
public static Integer execute(Collection<Integer> startStepNos, Map<Integer, WorkflowStep> stepMap, ListingHash<Integer, WorkflowLink> linksMapByFromStepNo)
{
//////////////////////////////////////////////////////////////////////////////////
// visited: map each node to a set of source step numbers that have reached it. //
//////////////////////////////////////////////////////////////////////////////////
ListingHash<Integer, Integer> visited = new ListingHash<>();
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Initialize the multi-source BFS queue. Each entry carries the current node id and its originating source. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
Deque<BFSQueueNode> queue = new ArrayDeque<>();
////////////////////////////////////////////////////////////////////////////////
// Seed the queue with each starting node, marking each as visited by itself. //
////////////////////////////////////////////////////////////////////////////////
for(Integer node : startStepNos)
{
visited.add(node, node);
queue.add(new BFSQueueNode(node, node));
}
///////////////////////////////////
// Perform the multi-source BFS. //
///////////////////////////////////
while(!queue.isEmpty())
{
BFSQueueNode firstNode = queue.removeFirst();
Integer current = firstNode.current;
Integer source = firstNode.source;
////////////////////////////////////////////////////////////////////
// Get the list of outgoing links/neighbors for the current node. //
////////////////////////////////////////////////////////////////////
List<WorkflowLink> neighbors = Objects.requireNonNullElse(linksMapByFromStepNo.get(current), Collections.emptyList());
for(WorkflowLink neighbor : neighbors)
{
Integer neighborId = neighbor.getToStepNo();
if(neighborId == null)
{
continue;
}
///////////////////////////////////////////////////////////////////////////
// Retrieve or initialize the set of sources that visited this neighbor. //
///////////////////////////////////////////////////////////////////////////
List<Integer> sourcesSet = visited.get(neighborId);
if(sourcesSet == null)
{
sourcesSet = new ArrayList<>();
visited.put(neighborId, sourcesSet);
}
/////////////////////////////////////////////////////////////////////
// If the current source has not visited the neighbor yet, add it. //
/////////////////////////////////////////////////////////////////////
if(!sourcesSet.contains(source))
{
sourcesSet.add(source);
//////////////////////////////////////////////////////////////
// Check if the neighbor was reached from multiple sources. //
// todo: should this be it has to equal all of them? //
// if(sourcesSet.size() == startStepNos.size()) //
//////////////////////////////////////////////////////////////
if(sourcesSet.size() > 1)
{
///////////////////////////////////////////////////////////////////////////////////////
// This is the first join point encountered – return the corresponding WorkflowStep. //
///////////////////////////////////////////////////////////////////////////////////////
return (stepMap.get(neighborId).getStepNo());
}
/////////////////////////////////////////////////////////////////
// Continue the BFS from this neighbor for the current source. //
/////////////////////////////////////////////////////////////////
queue.push(new BFSQueueNode(neighborId, source));
}
}
}
///////////////////////////////////////////////////////////////
// If no join point was found across all paths, return null. //
///////////////////////////////////////////////////////////////
return null;
}
/***************************************************************************
*
***************************************************************************/
private record BFSQueueNode(Integer current, Integer source)
{
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -41,8 +42,6 @@ public class WorkflowExecutionContext
private QBackendTransaction transaction;
private WorkflowRunLog workflowRunLog;
private WorkflowExecutor executor;
private boolean isTestRun = false;
@@ -236,26 +235,4 @@ public class WorkflowExecutionContext
return (this);
}
/*******************************************************************************
* Getter for executor
* @see #setExecutor(WorkflowExecutor)
*******************************************************************************/
WorkflowExecutor getExecutor()
{
return (this.executor);
}
/*******************************************************************************
* Setter for executor
* @param executor the object executing the workflow.
*******************************************************************************/
void setExecutor(WorkflowExecutor executor)
{
this.executor = executor;
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -30,7 +31,6 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Stack;
import java.util.concurrent.atomic.AtomicInteger;
import com.google.gson.reflect.TypeToken;
import com.kingsrook.qbits.workflows.definition.OutboundLinkMode;
import com.kingsrook.qbits.workflows.definition.WorkflowStepType;
@@ -71,7 +71,6 @@ public class WorkflowExecutor extends AbstractQActionBiConsumer<WorkflowInput, W
private WorkflowRunLog inputWorkflowRunLog;
private Stack<Integer> containerStack = new Stack<>();
private ExecutionPayload payload;
@@ -168,10 +167,31 @@ public class WorkflowExecutor extends AbstractQActionBiConsumer<WorkflowInput, W
///////////////
// step loop //
///////////////
ExecutionPayload payload = new ExecutionPayload(stepMap, linkMap, workflowTypeExecutor, new AtomicInteger(1), logStepList);
this.payload = payload;
context.setExecutor(this);
runStepLoop(workflowRevision.getStartStepNo(), context, null);
Integer stepNo = workflowRevision.getStartStepNo();
int seqNo = 1;
while(stepNo != null)
{
WorkflowStep step = stepMap.get(stepNo);
if(step == null)
{
throw new QException("Step not found by stepNo: " + stepNo);
}
WorkflowRunLogStep workflowRunLogStep = new WorkflowRunLogStep();
workflowRunLogStep.setWorkflowStepId(step.getId());
workflowRunLogStep.setSeqNo(seqNo);
workflowRunLogStep.setStartTimestamp(Instant.now());
logStepList.add(workflowRunLogStep);
WorkflowStepOutput workflowStepOutput = executeStep(step, workflowTypeExecutor, context);
workflowRunLogStep.setOutputData(ValueUtils.getValueAsString(workflowStepOutput.outputData()));
workflowRunLogStep.setMessage(workflowStepOutput.message());
stepNo = getNextStepNo(workflowStepOutput.outputData(), step, linkMap, stepMap, false);
workflowRunLogStep.setEndTimestamp(Instant.now());
seqNo++;
}
workflowTypeExecutor.postRun(context);
@@ -219,51 +239,6 @@ public class WorkflowExecutor extends AbstractQActionBiConsumer<WorkflowInput, W
/***************************************************************************
* @return true if the workflow should immediately stop
***************************************************************************/
boolean runStepLoop(Integer stepNo, WorkflowExecutionContext context, Integer stopStepNo) throws QException
{
while(stepNo != null)
{
WorkflowStep step = payload.stepMap().get(stepNo);
if(step == null)
{
throw new QException("Step not found by stepNo: " + stepNo);
}
WorkflowRunLogStep workflowRunLogStep = new WorkflowRunLogStep();
workflowRunLogStep.setWorkflowStepId(step.getId());
workflowRunLogStep.setStartTimestamp(Instant.now());
WorkflowStepOutput workflowStepOutput = executeStep(step, payload, context);
stepNo = getNextStepNo(workflowStepOutput.outputData(), step, payload.linkMap(), payload.stepMap(), false);
workflowRunLogStep.setEndTimestamp(Instant.now());
workflowRunLogStep.setOutputData(ValueUtils.getValueAsString(workflowStepOutput.outputData()));
workflowRunLogStep.setMessage(workflowStepOutput.message());
workflowRunLogStep.setSeqNo(payload.seqNo().getAndIncrement());
payload.logStepList().add(workflowRunLogStep);
if(stopStepNo != null && Objects.equals(stepNo, stopStepNo))
{
LOG.debug("Stopping at requested step", logPair("stopStepNo", stepNo));
return (false);
}
if(workflowStepOutput.shouldWorkflowImmediatelyStop())
{
LOG.info("Stopping step loop due to step output indicating workflow should immediately stop", logPair("stepNo", stepNo));
return (true);
}
}
return (false);
}
/***************************************************************************
**
***************************************************************************/
@@ -357,23 +332,13 @@ public class WorkflowExecutor extends AbstractQActionBiConsumer<WorkflowInput, W
}
}
Integer fromStepNo = fromStep.getStepNo();
List<WorkflowLink> links = linkMap.get(fromStepNo);
if(OutboundLinkMode.VARIABLE.equals(fromWorkflowStepType.getOutboundLinkMode()))
{
/////////////////////////////////////////////////////////////////////////////
// after a VARIABLE step (where its branches were all executed in //
// processMultiForkingStep), find the first join point, and step to there. //
/////////////////////////////////////////////////////////////////////////////
Integer joinStepNo = FindFirstJoinPoint.execute(links.stream().map(wl -> wl.getToStepNo()).toList(), stepMap, linkMap);
return (joinStepNo);
}
Integer fromStepNo = fromStep.getStepNo();
List<WorkflowLink> links = linkMap.get(fromStepNo);
Class<? extends Serializable> stepOutputClass = stepOutput == null ? null : stepOutput.getClass();
//////////////////////////////////////////////////////////////////////////////////////
// look for a link between the fromStepNo matching the stepOutput / condition value //
//////////////////////////////////////////////////////////////////////////////////////
List<String> evaluatedConditionValues = new ArrayList<>();
for(WorkflowLink link : CollectionUtils.nonNullList(links))
{
if(link.getConditionValue() == null)
@@ -385,29 +350,12 @@ public class WorkflowExecutor extends AbstractQActionBiConsumer<WorkflowInput, W
}
else
{
evaluatedConditionValues.add(link.getConditionValue());
try
{
Class<? extends Serializable> stepOutputClass = stepOutput == null ? null : stepOutput.getClass();
Serializable valueAsType = stepOutputClass == null ? link.getConditionValue() : ValueUtils.getValueAsType(stepOutputClass, link.getConditionValue());
if(Objects.equals(valueAsType, stepOutput))
{
////////////////////////////////////////////////////////////////////////////////
// a null link to-step no here may (always?) mean that a branch is empty. //
// but, rather than just returning null, we'll instead break this loop, so we //
// can be sure to hit the check for being inside a container before the end //
// of this method (which, if we're not in a container, will return null). //
// see testContainerEndingWithConditionalWithOneEmptySide //
////////////////////////////////////////////////////////////////////////////////
if(link.getToStepNo() != null)
{
return link.getToStepNo();
}
else
{
LOG.debug("Found link with condition value matching stepOutput, but no toStepNo - this may be expected for an empty branch within a container?", logPair("fromStepNo", link.getFromStepNo()), logPair("condition", link.getConditionValue()));
break;
}
return link.getToStepNo();
}
}
catch(Exception e)
@@ -417,11 +365,6 @@ public class WorkflowExecutor extends AbstractQActionBiConsumer<WorkflowInput, W
}
}
if(CollectionUtils.nullSafeHasContents(evaluatedConditionValues))
{
LOG.info("Evaluated at least 1 link condition value, but found no matches", logPair("evaluatedConditionValues", evaluatedConditionValues));
}
/////////////////////////////////////////////////////////////////////////
// if we didn't find a next-step, but there is something on the stack, //
// then look for a pop out of that frame //
@@ -442,7 +385,7 @@ public class WorkflowExecutor extends AbstractQActionBiConsumer<WorkflowInput, W
/***************************************************************************
**
***************************************************************************/
private WorkflowStepOutput executeStep(WorkflowStep step, ExecutionPayload payload, WorkflowExecutionContext context) throws QException
private WorkflowStepOutput executeStep(WorkflowStep step, WorkflowTypeExecutorInterface workflowTypeExecutor, WorkflowExecutionContext context) throws QException
{
WorkflowStepType workflowStepType = WorkflowsRegistry.of(QContext.getQInstance()).getWorkflowStepType(step.getWorkflowStepTypeName());
if(workflowStepType == null)
@@ -456,7 +399,8 @@ public class WorkflowExecutor extends AbstractQActionBiConsumer<WorkflowInput, W
}
WorkflowStepExecutorInterface workflowStepExecutor = QCodeLoader.getAdHoc(WorkflowStepExecutorInterface.class, workflowStepType.getExecutor());
payload.workflowTypeExecutor().preStep(step, context);
workflowTypeExecutor.preStep(step, context);
JSONObject jsonObject = JsonUtils.toJSONObject(step.getInputValuesJson());
Map<String, Object> mapValues = jsonObject.toMap();
@@ -471,23 +415,13 @@ public class WorkflowExecutor extends AbstractQActionBiConsumer<WorkflowInput, W
}
WorkflowStepOutput workflowStepOutput = workflowStepExecutor.execute(step, inputValues, context);
workflowStepOutput = payload.workflowTypeExecutor().postStep(step, context, workflowStepOutput);
workflowStepOutput = workflowTypeExecutor.postStep(step, context, workflowStepOutput);
return workflowStepOutput;
}
/***************************************************************************
*
***************************************************************************/
ExecutionPayload getPayload()
{
return (this.payload);
}
/***************************************************************************
**
***************************************************************************/
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -1,133 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.workflows.execution;
import java.io.Serializable;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import com.kingsrook.qbits.workflows.model.WorkflowLink;
import com.kingsrook.qbits.workflows.model.WorkflowRunLogStep;
import com.kingsrook.qbits.workflows.model.WorkflowStep;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/***************************************************************************
* interface for workflow step executors that are of a variable output type,
* and which can execute 0 or more of their outbound links.
***************************************************************************/
public interface WorkflowMultiForkingStepExecutorInterface extends WorkflowStepExecutorInterface
{
QLogger LOG = QLogger.getLogger(WorkflowMultiForkingStepExecutorInterface.class);
/***************************************************************************
* the Execute method for such steps should perform an executor.runStepLoop
* for each of the outbound links of the step (assuming they are supposed
* allowed to run).
***************************************************************************/
@Override
default WorkflowStepOutput execute(WorkflowStep step, Map<String, Serializable> inputValues, WorkflowExecutionContext context) throws QException
{
///////////////////////////////////////////////////////////////////////
// get the various sub-workflows ("fork links") to run from the step //
///////////////////////////////////////////////////////////////////////
WorkflowExecutor executor = context.getExecutor();
ExecutionPayload payload = executor.getPayload();
List<WorkflowLink> forkLinks = payload.linkMap().get(step.getStepNo());
preRun(step, inputValues, context);
sortForks(forkLinks, step, inputValues, context);
Integer stopStepNo = FindFirstJoinPoint.execute(forkLinks.stream().map(wl -> wl.getToStepNo()).toList(), payload.stepMap(), payload.linkMap());
boolean shouldWorkflowImmediatelyStop = false;
for(WorkflowLink link : forkLinks)
{
WorkflowRunLogStep workflowRunLogStep = new WorkflowRunLogStep();
workflowRunLogStep.setWorkflowStepId(step.getId());
workflowRunLogStep.setSeqNo(payload.seqNo().getAndIncrement());
workflowRunLogStep.setStartTimestamp(Instant.now());
WorkflowStepOutput workflowStepOutput = conditionallyExecuteFork(step, link, inputValues, context);
if(Objects.equals(workflowStepOutput.outputData(), true))
{
shouldWorkflowImmediatelyStop = executor.runStepLoop(link.getToStepNo(), context, stopStepNo);
}
workflowRunLogStep.setOutputData(ValueUtils.getValueAsString(workflowStepOutput.outputData()));
workflowRunLogStep.setMessage(workflowStepOutput.message());
workflowRunLogStep.setEndTimestamp(Instant.now());
payload.logStepList().add(workflowRunLogStep);
if(shouldWorkflowImmediatelyStop)
{
LOG.info("Stopping fork-link loop, due to step output indicating workflow should immediately stop", logPair("linkStartStepNo", link.getToStepNo()));
break;
}
}
WorkflowStepOutput workflowStepOutput = makeWorkflowStepOutput(step, inputValues, context);
postRun(step, inputValues, context);
if(shouldWorkflowImmediatelyStop)
{
workflowStepOutput = new WorkflowStepOutput(workflowStepOutput.outputData(), workflowStepOutput.message(), true);
}
return workflowStepOutput;
}
/***************************************************************************
* make sure the forks are executed in the order that the step expects.
***************************************************************************/
void sortForks(List<WorkflowLink> forkLinks, WorkflowStep step, Map<String, Serializable> inputValues, WorkflowExecutionContext context);
/***************************************************************************
* decide if a particular fork should be executed - and if so, then do so.
***************************************************************************/
WorkflowStepOutput conditionallyExecuteFork(WorkflowStep step, WorkflowLink fork, Map<String, Serializable> inputValues, WorkflowExecutionContext context) throws QException;
/***************************************************************************
* make output for the entire step, e.g., taking into account all of its
* forks.
***************************************************************************/
WorkflowStepOutput makeWorkflowStepOutput(WorkflowStep step, Map<String, Serializable> inputValues, WorkflowExecutionContext context) throws QException;
/***************************************************************************
* optional code to run before anything else in this interface
***************************************************************************/
default void preRun(WorkflowStep step, Map<String, Serializable> inputValues, WorkflowExecutionContext context) throws QException
{
}
/***************************************************************************
* optional code to run after anything else in this interface
***************************************************************************/
default void postRun(WorkflowStep step, Map<String, Serializable> inputValues, WorkflowExecutionContext context) throws QException
{
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -27,26 +28,16 @@ import java.io.Serializable;
/***************************************************************************
**
***************************************************************************/
public record WorkflowStepOutput(Serializable outputData, String message, boolean shouldWorkflowImmediatelyStop)
public record WorkflowStepOutput(Serializable outputData, String message)
{
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public WorkflowStepOutput(Serializable outputData, String message)
{
this(outputData, message, false);
}
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public WorkflowStepOutput(Serializable outputData)
{
this(outputData, null, false);
this(outputData, null);
}
@@ -57,7 +48,7 @@ public record WorkflowStepOutput(Serializable outputData, String message, boolea
*******************************************************************************/
public WorkflowStepOutput()
{
this(null, null, false);
this(null, null);
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.execution;
@@ -1,50 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.workflows.execution;
import java.util.List;
import com.kingsrook.qbits.workflows.model.WorkflowLink;
import com.kingsrook.qbits.workflows.model.WorkflowStep;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
/*******************************************************************************
* interface for the code that validates the overall structure of a workflow,
* e.g., when it's being saved. This would be where you'd validate relationships
* between steps (e.g., this step must have some other step within it)
*******************************************************************************/
public interface WorkflowTypeValidatorInterface
{
/***************************************************************************
* validate that a workflow can be saved.
*
* @param workflow the workflow record under which a new revision is being saved
* @param workflowRevision the new revision record that's been built
* @param workflowSteps list of workflow steps being saved
* @param workflowLinks list of workflow links being saved
* @param errors out param - any validation errors should be added to this list.
***************************************************************************/
void validate(QRecord workflow, QRecord workflowRevision, List<WorkflowStep> workflowSteps, List<WorkflowLink> workflowLinks, List<String> errors) throws QException;
}
@@ -1,71 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.workflows.execution.nodes;
import java.util.ArrayList;
import java.util.List;
/***************************************************************************
* list of nodes in a graph representation of a workflow
***************************************************************************/
public record NodeSequence(List<StepNode> nodes)
{
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public NodeSequence()
{
this(new ArrayList<>());
}
/***************************************************************************
*
***************************************************************************/
public void add(StepNode node)
{
nodes.add(node);
}
/***************************************************************************
*
***************************************************************************/
public StepNode get(int i)
{
return nodes.get(i);
}
/***************************************************************************
*
***************************************************************************/
public int size()
{
return (nodes.size());
}
}
@@ -1,46 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.workflows.execution.nodes;
import java.util.HashMap;
import java.util.Map;
import com.kingsrook.qbits.workflows.model.WorkflowStep;
/***************************************************************************
* Single step, plus, if it opens a nested scope (e.g., branches, or a container)
* then those subSequences, along with their conditionalValues (guards/labels)
* as map keys
***************************************************************************/
public record StepNode(WorkflowStep step, Map<String, NodeSequence> subSequences)
{
/*******************************************************************************
** Constructor for step w/o subSequences
**
*******************************************************************************/
public StepNode(WorkflowStep step)
{
this(step, new HashMap<>());
}
}
@@ -1,223 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.workflows.execution.nodes;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import com.kingsrook.qbits.workflows.definition.OutboundLinkMode;
import com.kingsrook.qbits.workflows.definition.WorkflowStepType;
import com.kingsrook.qbits.workflows.definition.WorkflowsRegistry;
import com.kingsrook.qbits.workflows.execution.FindFirstJoinPoint;
import com.kingsrook.qbits.workflows.model.WorkflowLink;
import com.kingsrook.qbits.workflows.model.WorkflowStep;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ListingHash;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
* Class that can convert a list of steps & links (as a workflow revision is
* stored in a database) to a hierarchical graph data structure - a list of nodes,
* each of which is a step, and optionally a map of lists of sub-branches
* (which can be more useful for various kinds of processing).
*******************************************************************************/
public class StepNodeGraphBuilder
{
private static final QLogger LOG = QLogger.getLogger(StepNodeGraphBuilder.class);
/***************************************************************************
*
***************************************************************************/
public static NodeSequence buildNodeSequence(Integer startStepNo, List<WorkflowStep> workflowSteps, List<WorkflowLink> workflowLinks)
{
Map<Integer, WorkflowStep> stepMap = CollectionUtils.listToMap(workflowSteps, ws -> ws.getStepNo());
ListingHash<Integer, WorkflowLink> linksFromMap = new ListingHash<>();
Map<Integer, Set<Integer>> linksToMap = new HashMap<>();
for(WorkflowLink workflowLink : workflowLinks)
{
linksFromMap.add(workflowLink.getFromStepNo(), workflowLink);
if(workflowLink.getToStepNo() != null)
{
Set<Integer> subToSet = linksToMap.computeIfAbsent(workflowLink.getToStepNo(), k -> new HashSet<>());
subToSet.add(workflowLink.getFromStepNo());
}
}
NodeSequence nodeSequence = new NodeSequence();
buildNodeSequence(nodeSequence, stepMap.get(startStepNo), stepMap, linksFromMap, null, 0);
return (nodeSequence);
}
/***************************************************************************
*
***************************************************************************/
private static void buildNodeSequence(NodeSequence nodeSequence, WorkflowStep startStep, Map<Integer, WorkflowStep> stepMap, ListingHash<Integer, WorkflowLink> linksFromMap, Integer joinStepNo, Integer depth)
{
WorkflowsRegistry workflowsRegistry = WorkflowsRegistry.of(QContext.getQInstance());
WorkflowStep currentWorkflowStep = startStep;
while(currentWorkflowStep != null)
{
WorkflowStepType workflowStepType = workflowsRegistry.getWorkflowStepType(currentWorkflowStep.getWorkflowStepTypeName());
if(workflowStepType == null)
{
LOG.warn("Unrecognized workflow step type: " + currentWorkflowStep.getWorkflowStepTypeName());
return;
}
if(Objects.equals(currentWorkflowStep.getStepNo(), joinStepNo))
{
//////////////////////////////////////////////////////////////////////////////
// if we've reached the join step, it's the end of this sequence, so return //
//////////////////////////////////////////////////////////////////////////////
return;
}
StepNode stepNode = new StepNode(currentWorkflowStep);
nodeSequence.add(stepNode);
List<WorkflowLink> outboundLinks = linksFromMap.get(currentWorkflowStep.getStepNo());
if(CollectionUtils.nullSafeIsEmpty(outboundLinks))
{
/////////////////////////////////////////////////////////////////////////////
// if no outbound links, then we're at the end of this sequence, so return //
/////////////////////////////////////////////////////////////////////////////
return;
}
else if(outboundLinks.size() == 1 && !StringUtils.hasContent(outboundLinks.get(0).getConditionValue())) // ?? avoid being tripped by a branch w/ only 1 side populated at the end of the graph
{
///////////////////////////////////////////////////////////////////
// 1 outbound link here means we're not in a switch/if-else/fork //
///////////////////////////////////////////////////////////////////
WorkflowLink outboundLink = outboundLinks.get(0);
currentWorkflowStep = outboundLink.getToStepNo() == null ? null : stepMap.get(outboundLink.getToStepNo());
////////////////////////////////
// todo is this == null okay? //
////////////////////////////////
if(currentWorkflowStep == null || currentWorkflowStep.getStepNo().equals(joinStepNo))
{
///////////////////////////////////////////////////////
// this means we're ending a nested (recursive) call //
///////////////////////////////////////////////////////
return;
}
}
else
{
if(workflowStepType.getOutboundLinkMode().equals(OutboundLinkMode.TWO) || workflowStepType.getOutboundLinkMode().equals(OutboundLinkMode.VARIABLE))
{
///////////////////////////////////////////////////////////////////////////////////////////////////
// this means we are starting a fork. find where it ends - where the branches first re-converge //
///////////////////////////////////////////////////////////////////////////////////////////////////
Integer branchJoin = FindFirstJoinPoint.executeForLinks(outboundLinks, stepMap, linksFromMap);
////////////////////////////////////////////
// for each branch, make a recursive call //
////////////////////////////////////////////
for(WorkflowLink outboundLink : outboundLinks)
{
String conditionValue = outboundLink.getConditionValue();
NodeSequence branchSequence = new NodeSequence();
stepNode.subSequences().put(conditionValue, branchSequence);
if(branchJoin != null && Objects.equals(branchJoin, outboundLink.getToStepNo()))
{
////////////////////////////////////////////////////////////////////////
// this indicates an empty branch - so continue to next outbound link //
////////////////////////////////////////////////////////////////////////
continue;
}
///////////////////////////////////////////////////////////////////////////////////////////
// if this is a step out of the branch (e.g., to a join), then don't process it in here. //
// also though, this line doesn't actually get hit, so... //
///////////////////////////////////////////////////////////////////////////////////////////
if(Objects.equals(outboundLink.getToStepNo(), joinStepNo))
{
continue;
}
else
{
if(outboundLink.getToStepNo() != null)
{
buildNodeSequence(branchSequence, stepMap.get(outboundLink.getToStepNo()), stepMap, linksFromMap, branchJoin, depth + 1);
}
}
}
////////////////////////////////////////////////////////////////////////////////
// after handling all the branches, then continue the loop with the join step //
////////////////////////////////////////////////////////////////////////////////
currentWorkflowStep = branchJoin == null ? null : stepMap.get(branchJoin);
}
else if(workflowStepType.getOutboundLinkMode().equals(OutboundLinkMode.CONTAINER))
{
///////////////////////////////////////////////////////////////////////
// if the container is non-empty, there should be a push link for it //
///////////////////////////////////////////////////////////////////////
Optional<WorkflowLink> pushLink = outboundLinks.stream().filter(ol -> "push".equals(ol.getConditionValue())).findFirst();
if(pushLink.isPresent())
{
if(pushLink.get().getToStepNo() != null)
{
NodeSequence containerSequence = new NodeSequence();
stepNode.subSequences().put("push", containerSequence);
buildNodeSequence(containerSequence, stepMap.get(pushLink.get().getToStepNo()), stepMap, linksFromMap, null, depth + 1);
}
}
Optional<WorkflowLink> popLink = outboundLinks.stream().filter(ol -> "pop".equals(ol.getConditionValue())).findFirst();
if(popLink.isPresent())
{
if(popLink.get().getToStepNo() != null)
{
currentWorkflowStep = stepMap.get(popLink.get().getToStepNo());
}
}
else
{
return;
}
}
else
{
LOG.warn("expected to have multiple outbound links for a step of type: " + currentWorkflowStep.getWorkflowStepTypeName());
}
}
}
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -24,10 +25,8 @@ package com.kingsrook.qbits.workflows.implementations.recordworkflows;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.kingsrook.qbits.workflows.definition.OutboundLinkMode;
import com.kingsrook.qbits.workflows.definition.OutboundLinkOption;
import com.kingsrook.qbits.workflows.definition.WorkflowStepType;
@@ -40,7 +39,6 @@ import com.kingsrook.qbits.workflows.model.Workflow;
import com.kingsrook.qbits.workflows.model.WorkflowRevision;
import com.kingsrook.qbits.workflows.model.WorkflowStep;
import com.kingsrook.qqq.api.actions.GetTableApiFieldsAction;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
import com.kingsrook.qqq.api.utils.ApiQueryFilterUtils;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.context.QContext;
@@ -56,7 +54,6 @@ 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.modules.backend.implementations.utils.BackendQueryFilterUtils;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
@@ -109,10 +106,8 @@ public class InputRecordFilterStep extends WorkflowStepType implements WorkflowS
**
***************************************************************************/
@Override
public WorkflowStepOutput execute(WorkflowStep step, Map<String, Serializable> inputValues, WorkflowExecutionContext workflowExecutionContext) throws QException
public WorkflowStepOutput execute(WorkflowStep step, Map<String, Serializable> inputValues, WorkflowExecutionContext context) throws QException
{
RecordWorkflowContext context = (RecordWorkflowContext) workflowExecutionContext;
QRecord record = (QRecord) context.getValues().get("record");
if(record == null)
{
@@ -135,38 +130,26 @@ public class InputRecordFilterStep extends WorkflowStepType implements WorkflowS
///////////////////////////////////////////////////////////////////////////////
filter.applyCriteriaOptionToAllCriteria(CriteriaOption.CASE_INSENSITIVE);
List<QRecordWithJoinedRecords> recordWithJoins = buildCrossProduct(record, filter, context.recordWorkflowContextJoinedRecordHelper.get());
List<QRecordWithJoinedRecords> recordWithJoins = buildCrossProduct(record, filter, context);
return evaluateCrossProduct(recordWithJoins, filter);
}
/***************************************************************************
* For a given record, and a filter, and a {@link RecordWorkflowContextJoinedRecordHelper}
* object from a live workflow context, build a List of {@link QRecordWithJoinedRecords}
* that can be used to evaluate if a record (with joins) matches a filter.
*
* <p>Note that this method is public, and called by application-defined workflows.
* In other words, this method is part of our published API in here!</p>
*
* @param record the main record in the workflow
* @param filter the filter which may have joins (thus the need for a cross-product)
* @param joinedRecordHelper object from context of the workflow that knows
* about cached join records and records being inserted
* and/or deleted.
* @return {@code List<QRecordWithJoinedRecords>} the cross product that the
* filter should be evaluated against.
***************************************************************************/
public static List<QRecordWithJoinedRecords> buildCrossProduct(QRecord record, QQueryFilter filter, RecordWorkflowContextJoinedRecordHelper joinedRecordHelper) throws QException
public List<QRecordWithJoinedRecords> buildCrossProduct(QRecord record, QQueryFilter filter, WorkflowExecutionContext workflowExecutionContext) throws QException
{
RecordWorkflowContext context = (RecordWorkflowContext) workflowExecutionContext;
List<QRecordWithJoinedRecords> crossProduct = new ArrayList<>();
crossProduct.add(new QRecordWithJoinedRecords(record));
List<QueryJoin> joinsInFilter = BackendQueryFilterUtils.identifyJoinsInFilter(joinedRecordHelper.getTableName(), filter);
joinsInFilter = sortQueryJoinsFromMainTableOutward(joinedRecordHelper.getTableName(), joinsInFilter);
List<QueryJoin> joinsInFilter = BackendQueryFilterUtils.identifyJoinsInFilter(context.getWorkflow().getTableName(), filter);
for(QueryJoin join : joinsInFilter)
{
crossProduct = expandCrossProductViaJoin(crossProduct, join, joinedRecordHelper);
crossProduct = expandCrossProductViaJoin(crossProduct, join, context);
}
return crossProduct;
@@ -174,96 +157,16 @@ public class InputRecordFilterStep extends WorkflowStepType implements WorkflowS
/*******************************************************************************
* Sort the query joins so that they fan outward from the main table to ones
* farther away.
*
* <p>This is so, if we've got a -> b -> c -- we'll process 'b' before 'c',
* as we need the records in 'b' to find the records in 'c'.</p>
*
* <p><i>Note that this method was copied from {@code AbstractRDBMSAction}</i></p>
*******************************************************************************/
private static List<QueryJoin> sortQueryJoinsFromMainTableOutward(String mainTableName, List<QueryJoin> queryJoins)
{
List<QueryJoin> rs = new ArrayList<>();
////////////////////////////////////////////////////////////////////////////////
// make a copy of the input list that we can feel safe removing elements from //
////////////////////////////////////////////////////////////////////////////////
List<QueryJoin> inputListCopy = new ArrayList<>(queryJoins);
///////////////////////////////////////////////////////////////////////////////////////////////////
// keep track of the tables (or aliases) that we've seen - that's what we'll "grow" outward from //
///////////////////////////////////////////////////////////////////////////////////////////////////
Set<String> seenTablesOrAliases = new HashSet<>();
seenTablesOrAliases.add(mainTableName);
////////////////////////////////////////////////////////////////////////////////////
// loop as long as there are more tables in the inputList, and the keepGoing flag //
// is set (e.g., indicating that we added something in the last iteration) //
////////////////////////////////////////////////////////////////////////////////////
boolean keepGoing = true;
while(!inputListCopy.isEmpty() && keepGoing)
{
keepGoing = false;
Iterator<QueryJoin> iterator = inputListCopy.iterator();
while(iterator.hasNext())
{
QueryJoin nextQueryJoin = iterator.next();
//////////////////////////////////////////////////////////////////////////
// get the baseTableOrAlias from this join - and if it isn't set in the //
// QueryJoin, then get it from the left-side of the join's metaData //
//////////////////////////////////////////////////////////////////////////
String baseTableOrAlias = nextQueryJoin.getBaseTableOrAlias();
if(baseTableOrAlias == null && nextQueryJoin.getJoinMetaData() != null)
{
baseTableOrAlias = nextQueryJoin.getJoinMetaData().getLeftTable();
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// if we have a baseTableOrAlias (would we ever not?), and we've seen it before - OR - we've seen this query join's joinTableOrAlias, //
// then we can add this pair of namesOrAliases to our seen-set, remove this queryJoin from the inputListCopy (iterator), and keep going //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if((StringUtils.hasContent(baseTableOrAlias) && seenTablesOrAliases.contains(baseTableOrAlias)) || seenTablesOrAliases.contains(nextQueryJoin.getJoinTableOrItsAlias()))
{
rs.add(nextQueryJoin);
if(StringUtils.hasContent(baseTableOrAlias))
{
seenTablesOrAliases.add(baseTableOrAlias);
}
seenTablesOrAliases.add(nextQueryJoin.getJoinTableOrItsAlias());
iterator.remove();
keepGoing = true;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
// in case any are left, add them all here - does this ever happen? //
// the only time a conditional breakpoint here fires in the RDBMS test suite, is in query designed to throw. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
rs.addAll(inputListCopy);
return (rs);
}
/***************************************************************************
*
***************************************************************************/
private static List<QRecordWithJoinedRecords> expandCrossProductViaJoin(List<QRecordWithJoinedRecords> recordWithJoinedRecords, QueryJoin queryJoin, RecordWorkflowContextJoinedRecordHelper joinedRecordHelper) throws QException
private static List<QRecordWithJoinedRecords> expandCrossProductViaJoin(List<QRecordWithJoinedRecords> orderWithJoinedRecords, QueryJoin queryJoin, RecordWorkflowContext context) throws QException
{
String joinTableName = queryJoin.getJoinTable();
List<QRecord> recordsToBeInserted = joinedRecordHelper.getRecordsToInsert(joinTableName);
List<QRecord> recordsAlreadyInBackend = joinedRecordHelper.getJoinRecords(queryJoin);
Set<Serializable> idsToDelete = joinedRecordHelper.getPrimaryKeysToDelete(joinTableName);
String primaryKeyField = QContext.getQInstance().getTable(joinTableName).getPrimaryKeyField();
String joinTableName = queryJoin.getJoinTable();
ArrayList<QRecord> recordsToBeInserted = context.recordsToInsert.get().computeIfAbsent(joinTableName, k -> new ArrayList<>());
List<QRecord> recordsAlreadyInBackend = context.getJoinRecords(queryJoin);
HashSet<Serializable> idsToDelete = context.primaryKeysToDelete.get().computeIfAbsent(joinTableName, k -> new HashSet<>());
String primaryKeyField = QContext.getQInstance().getTable(joinTableName).getPrimaryKeyField();
//////////////////////////////////////////////////////////////////////////////////////////////////////
// add records that already existed to the cross product, filtering out ones that are to be deleted //
@@ -282,8 +185,8 @@ public class InputRecordFilterStep extends WorkflowStepType implements WorkflowS
////////////////////////////////////
CollectionUtils.addAllIfNotNull(recordsToCross, recordsToBeInserted);
recordWithJoinedRecords = makeCrossProduct(recordWithJoinedRecords, joinTableName, recordsToCross);
return recordWithJoinedRecords;
orderWithJoinedRecords = makeCrossProduct(orderWithJoinedRecords, joinTableName, recordsToCross);
return orderWithJoinedRecords;
}
@@ -291,21 +194,18 @@ public class InputRecordFilterStep extends WorkflowStepType implements WorkflowS
/***************************************************************************
*
***************************************************************************/
private static List<QRecordWithJoinedRecords> makeCrossProduct(List<QRecordWithJoinedRecords> recordsWithJoinedRecords, String joinTableName, List<QRecord> joinRecordsToCross)
private static List<QRecordWithJoinedRecords> makeCrossProduct(List<QRecordWithJoinedRecords> orderWithJoinedRecords, String joinTableName, List<QRecord> joinRecodsToCross)
{
if(recordsWithJoinedRecords.isEmpty() || joinRecordsToCross.isEmpty())
if(!joinRecodsToCross.isEmpty())
{
return new ArrayList<>();
List<QRecordWithJoinedRecords> newCrossProduct = new ArrayList<>();
for(QRecordWithJoinedRecords orderWithJoinedRecord : orderWithJoinedRecords)
{
newCrossProduct.addAll(orderWithJoinedRecord.buildCrossProduct(joinTableName, joinRecodsToCross));
}
orderWithJoinedRecords = newCrossProduct;
}
List<QRecordWithJoinedRecords> newCrossProduct = new ArrayList<>();
for(QRecordWithJoinedRecords recordWithJoinedRecord : recordsWithJoinedRecords)
{
newCrossProduct.addAll(recordWithJoinedRecord.buildCrossProduct(joinTableName, joinRecordsToCross));
}
recordsWithJoinedRecords = newCrossProduct;
return recordsWithJoinedRecords;
return orderWithJoinedRecords;
}
@@ -354,7 +254,7 @@ public class InputRecordFilterStep extends WorkflowStepType implements WorkflowS
String apiName = workflowRevision.getValueString("apiName");
String apiVersion = workflowRevision.getValueString("apiVersion");
String tableName = workflow.getValueString("tableName");
Map<String, QFieldMetaData> tableApiFields = GetTableApiFieldsAction.getTableApiFieldMap(new GetTableApiFieldsInput().withApiName(apiName).withVersion(apiVersion).withTableName(tableName));
Map<String, QFieldMetaData> tableApiFields = GetTableApiFieldsAction.getTableApiFieldMap(new GetTableApiFieldsAction.ApiNameVersionAndTableName(apiName, apiVersion, tableName));
ArrayList<String> badRequestMessages = new ArrayList<>();
ApiQueryFilterUtils.manageCriteriaFields(filter, tableApiFields, badRequestMessages, apiName, apiVersion, new QueryInput(tableName).withFilter(filter));
errors.addAll(badRequestMessages);
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -24,16 +25,24 @@ package com.kingsrook.qbits.workflows.implementations.recordworkflows;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import com.kingsrook.qbits.workflows.execution.LazyInitObjectInWorkflowContext;
import com.google.gson.reflect.TypeToken;
import com.kingsrook.qbits.workflows.execution.ObjectInWorkflowContext;
import com.kingsrook.qbits.workflows.execution.WorkflowExecutionContext;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
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.joins.JoinOn;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
/*******************************************************************************
@@ -48,43 +57,17 @@ public class RecordWorkflowContext extends WorkflowExecutionContext
public final ObjectInWorkflowContext<HashMap<String, ArrayList<QRecord>>> recordsToInsert = new ObjectInWorkflowContext<>(this, "recordsToInsert", new HashMap<>());
public final ObjectInWorkflowContext<HashMap<String, HashSet<Serializable>>> primaryKeysToDelete = new ObjectInWorkflowContext<>(this, "primaryKeysToDelete", new HashMap<>());
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// records already stored in the backend that are joined with the main record - they used to be stored in a map kept //
// right here, but they've migrated into the RecordWorkflowContextJoinedRecordHelper. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
public final ObjectInWorkflowContext<RecordWorkflowContextJoinedRecordHelper> recordWorkflowContextJoinedRecordHelper = new LazyInitObjectInWorkflowContext<>(this, "recordWorkflowContextJoinedRecordHelper", () -> newRecordWorkflowContextJoinedRecordHelper(this));
///////////////////////////////////////////////////////////////////////////////////////////
// records already stored in the backend that are joined with the main record. //
// should only be accessed via the getRelatedRecords method, which lazy inits (by query) //
///////////////////////////////////////////////////////////////////////////////////////////
private final ObjectInWorkflowContext<HashMap<JoinKey, ArrayList<QRecord>>> joinRecords = new ObjectInWorkflowContext<>(this, "joinRecords", new HashMap<>());
/***************************************************************************
* Construct a new RecordWorkflowContextJoinedRecordHelper, which uses this
* context class's members to get the list of records to insert and the
* set of ids to delete
***************************************************************************/
private RecordWorkflowContextJoinedRecordHelper newRecordWorkflowContextJoinedRecordHelper(RecordWorkflowContext recordWorkflowContext)
private record JoinKey(String joinName, ArrayList<Serializable> joinValues)
{
return new RecordWorkflowContextJoinedRecordHelper(getWorkflow().getTableName(), record.get())
{
/***************************************************************************
*
***************************************************************************/
@Override
public List<QRecord> getRecordsToInsert(String tableName)
{
return recordWorkflowContext.recordsToInsert.get().computeIfAbsent(tableName, (k) -> new ArrayList<>());
}
/***************************************************************************
*
***************************************************************************/
@Override
public Set<Serializable> getPrimaryKeysToDelete(String tableName)
{
return recordWorkflowContext.primaryKeysToDelete.get().computeIfAbsent(tableName, (k) -> new HashSet<>());
}
};
}
@@ -164,11 +147,81 @@ public class RecordWorkflowContext extends WorkflowExecutionContext
/***************************************************************************
* allow join-records, which are normally managed by the recordWorkflowContextJoinedRecordHelper
* to be explicitly set in that object. Useful for test scenarios, maybe more.
*
***************************************************************************/
public void setJoinRecords(QueryJoin queryJoin, List<QRecord> associatedRecords)
public List<QRecord> getJoinRecords(QueryJoin queryJoin) throws QException
{
recordWorkflowContextJoinedRecordHelper.get().setJoinRecords(queryJoin, associatedRecords);
JoinRecordsKeyConstruction result = getJoinRecordsKeyConstruction(queryJoin, record.get());
if(joinRecords.get().get(result.key()) == null)
{
ArrayList<QRecord> records = CollectionUtils.useOrWrap(result.makeEmpty() ? Collections.emptyList() : QueryAction.execute(queryJoin.getJoinTable(), result.filter()), new TypeToken<>() {});
joinRecords.get().put(result.key(), records);
}
return joinRecords.get().get(result.key());
}
/***************************************************************************
*
***************************************************************************/
private JoinRecordsKeyConstruction getJoinRecordsKeyConstruction(QueryJoin queryJoin, QRecord mainRecord)
{
ArrayList<Serializable> joinValues = new ArrayList<>();
JoinKey key = new JoinKey(queryJoin.getJoinMetaData().getName(), joinValues);
String baseTableName = getWorkflow().getTableName();
QQueryFilter filter = new QQueryFilter();
boolean makeEmpty = false;
for(JoinOn joinOn : queryJoin.getJoinMetaData().getJoinOns())
{
Serializable mainTableValue;
String joinTableField;
if(queryJoin.getJoinMetaData().getLeftTable().equals(baseTableName))
{
mainTableValue = mainRecord.getValue(joinOn.getLeftField());
joinTableField = joinOn.getRightField();
}
else
{
mainTableValue = mainRecord.getValue(joinOn.getRightField());
joinTableField = joinOn.getLeftField();
}
key.joinValues.add(mainTableValue);
if(mainTableValue == null)
{
// throw (new QException("Missing join value in " + baseTableName + " getting related " + tableName + " records"));
makeEmpty = true;
}
filter.addCriteria(new QFilterCriteria(joinTableField, QCriteriaOperator.EQUALS, mainTableValue));
}
JoinRecordsKeyConstruction result = new JoinRecordsKeyConstruction(key, filter, makeEmpty);
return result;
}
/***************************************************************************
*
***************************************************************************/
private record JoinRecordsKeyConstruction(JoinKey key, QQueryFilter filter, boolean makeEmpty)
{
}
/***************************************************************************
*
***************************************************************************/
public void setJoinRecords(QueryJoin queryJoin, QRecord mainRecord, List<QRecord> records)
{
ArrayList<QRecord> recordArrayList = CollectionUtils.useOrWrap(records, new TypeToken<>() {});
JoinRecordsKeyConstruction result = getJoinRecordsKeyConstruction(queryJoin, mainRecord);
joinRecords.get().put(result.key(), recordArrayList);
}
}
@@ -1,237 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2026. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.workflows.implementations.recordworkflows;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import com.google.gson.reflect.TypeToken;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryJoin;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
/*******************************************************************************
* For workflows that are centered around a single record and may work with
* records joined to that record, an instance of this class can be put into
* the workflow's context. It can then be used, specifically, by the
* {@link InputRecordFilterStep} to build up a cross-product used to do in-
* memory filter operation.
*******************************************************************************/
public class RecordWorkflowContextJoinedRecordHelper implements Serializable
{
private final String tableName;
private final QRecord record;
private Map<String, List<QRecord>> joinRecords = new HashMap<>();
/*******************************************************************************
** Constructor
**
*******************************************************************************/
public RecordWorkflowContextJoinedRecordHelper(String tableName, QRecord record)
{
this.tableName = tableName;
this.record = record;
}
/***************************************************************************
* if the context is collecting records that will be inserted when the
* workflow is done, and those need to be included, e.g., for in-memory
* filters - then override this method (e.g., via an anonymous inner class
* in the specific workflow context class in question), and return appropriate
* pending-insertion records here.
***************************************************************************/
public List<QRecord> getRecordsToInsert(String tableName)
{
return Collections.emptyList();
}
/***************************************************************************
* if the context is collecting primary keys that will be deleted when the
* workflow is done, and those records need to be excluded, e.g., for in-memory
* filters - then override this method (e.g., via an anonymous inner class
* in the specific workflow context class in question), and return appropriate
* pending-deletion keys here.
***************************************************************************/
public Set<Serializable> getPrimaryKeysToDelete(String tableName)
{
return Collections.emptySet();
}
/***************************************************************************
* Clear the join records map. Specifically useful if the context is being
* used both during and after workflow execution (e.g. before and after
* records have been stored).
***************************************************************************/
public void reset()
{
joinRecords.clear();
}
/***************************************************************************
* get the join records for a specific query join against the main record
* of the workflow.
*
* <p>internally this method does cache those lookups - see {@code reset()}
* to clear that cache.</p>
***************************************************************************/
public List<QRecord> getJoinRecords(QueryJoin queryJoin) throws QException
{
String joinTableOrAlias = queryJoin.getJoinTableOrItsAlias();
if(!StringUtils.hasContent(joinTableOrAlias))
{
throw (new QException("Missing joinTableOrAlias in queryJoin: " + queryJoin));
}
List<QRecord> baseRecords;
if(queryJoin.getBaseTableOrAlias().equals(tableName))
{
baseRecords = List.of(record);
}
else
{
baseRecords = Objects.requireNonNullElseGet(joinRecords.get(queryJoin.getBaseTableOrAlias()), ArrayList::new);
}
QQueryFilter joinRecordsFilter = getJoinRecordsFilter(queryJoin, baseRecords);
if(joinRecords.get(joinTableOrAlias) == null)
{
List<QRecord> records = joinRecordsFilter == null ? Collections.emptyList() : QueryAction.execute(queryJoin.getJoinTable(), joinRecordsFilter);
joinRecords.put(joinTableOrAlias, records);
}
return joinRecords.get(joinTableOrAlias);
}
/***************************************************************************
* For a given queryJoin and a list of 'base records', build a filter that
* can find join records.
*
* <p>Note that this will potentially be a (a=? AND b=?) OR (a=? AND b=?)
* style query... </p>
*
* @return filter to find join records - or - null if there should be no
* join records returned (e.g., empty base record list).
***************************************************************************/
protected QQueryFilter getJoinRecordsFilter(QueryJoin queryJoin, List<QRecord> baseRecords)
{
String baseTableName = queryJoin.getBaseTableOrAlias();
if(CollectionUtils.nullSafeIsEmpty(baseRecords))
{
return (null);
}
QQueryFilter filter = new QQueryFilter().withBooleanOperator(QQueryFilter.BooleanOperator.OR);
for(QRecord baseRecord : baseRecords)
{
QQueryFilter subFilter = new QQueryFilter();
filter.addSubFilter(subFilter);
for(JoinOn joinOn : queryJoin.getJoinMetaData().getJoinOns())
{
Serializable mainTableValue;
String joinTableField;
if(queryJoin.getJoinMetaData().getLeftTable().equals(baseTableName))
{
mainTableValue = baseRecord.getValue(joinOn.getLeftField());
joinTableField = joinOn.getRightField();
}
else
{
mainTableValue = baseRecord.getValue(joinOn.getRightField());
joinTableField = joinOn.getLeftField();
}
if(mainTableValue == null)
{
subFilter.addCriteria(new QFilterCriteria(joinTableField, QCriteriaOperator.FALSE));
}
else
{
subFilter.addCriteria(new QFilterCriteria(joinTableField, QCriteriaOperator.EQUALS, mainTableValue));
}
}
}
return (filter);
}
/***************************************************************************
*
***************************************************************************/
public void setJoinRecords(QueryJoin queryJoin, List<QRecord> joinRecords)
{
ArrayList<QRecord> recordArrayList = CollectionUtils.useOrWrap(joinRecords, new TypeToken<>() {});
this.joinRecords.put(queryJoin.getJoinTableOrItsAlias(), recordArrayList);
}
/*******************************************************************************
** Getter for tableName
**
*******************************************************************************/
public String getTableName()
{
return tableName;
}
/*******************************************************************************
** Getter for record
**
*******************************************************************************/
public QRecord getRecord()
{
return record;
}
}
@@ -6,17 +6,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -97,11 +98,6 @@ public class RecordWorkflowInputRecordFilterWidget extends AbstractWidgetRendere
public static void applyApiNameAndVersionFromInputToWidgetData(RenderWidgetInput input, FilterAndColumnsSetupData widgetData)
{
String apiName = input.getQueryParams().get("workflowRevision.apiName");
if(!StringUtils.hasContent(apiName))
{
apiName = input.getQueryParams().get("apiName");
}
if(StringUtils.hasContent(apiName))
{
widgetData.setApiName(apiName);
@@ -117,11 +113,6 @@ public class RecordWorkflowInputRecordFilterWidget extends AbstractWidgetRendere
}
String apiVersion = input.getQueryParams().get("workflowRevision.apiVersion");
if(!StringUtils.hasContent(apiVersion))
{
apiVersion = input.getQueryParams().get("apiVersion");
}
if(StringUtils.hasContent(apiVersion))
{
widgetData.setApiVersion(apiVersion);
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -115,10 +116,9 @@ public class RecordWorkflowTypeTester implements WorkflowTypeTesterInterface
QueryJoin queryJoin = new QueryJoin()
.withJoinTable(associatedTableName)
.withBaseTableOrAlias(tableName)
.withJoinMetaData(QContext.getQInstance().getJoin(association.get().getJoinName()));
recordWorkflowContext.setJoinRecords(queryJoin, associatedRecords);
recordWorkflowContext.setJoinRecords(queryJoin, inputRecord, associatedRecords);
}
}
}
@@ -198,12 +198,6 @@ public class RecordWorkflowTypeTester implements WorkflowTypeTesterInterface
{
RecordWorkflowContext context = (RecordWorkflowContext) workflowOutput.getContext();
/////////////////////////////////////////////////////////////////////////////////////////////////
// reset the pre-looked up joins in this object (since after the workflow is complete, records //
// will be stored, which wouldn't have been before, so it might have cached old record lists.) //
/////////////////////////////////////////////////////////////////////////////////////////////////
context.recordWorkflowContextJoinedRecordHelper.get().reset();
QRecord record = getRecordForTestAssertionFilter(workflowOutput, assertion);
if(record == null)
{
@@ -216,7 +210,7 @@ public class RecordWorkflowTypeTester implements WorkflowTypeTesterInterface
// re-use the order filtering logic of InputRecordFilterStep (e.g., building joins) //
//////////////////////////////////////////////////////////////////////////////////////
InputRecordFilterStep inputRecordFilterStep = new InputRecordFilterStep();
List<QRecordWithJoinedRecords> orderWithJoinedRecords = InputRecordFilterStep.buildCrossProduct(record, filter, context.recordWorkflowContextJoinedRecordHelper.get());
List<QRecordWithJoinedRecords> orderWithJoinedRecords = inputRecordFilterStep.buildCrossProduct(record, filter, context);
WorkflowStepOutput workflowStepOutput = inputRecordFilterStep.evaluateCrossProduct(orderWithJoinedRecords, filter);
return (Objects.equals(workflowStepOutput.outputData(), true));
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -30,7 +31,6 @@ import com.kingsrook.qbits.workflows.execution.WorkflowExecutionContext;
import com.kingsrook.qbits.workflows.model.Workflow;
import com.kingsrook.qbits.workflows.model.WorkflowRevision;
import com.kingsrook.qqq.api.actions.GetTableApiFieldsAction;
import com.kingsrook.qqq.api.model.actions.GetTableApiFieldsInput;
import com.kingsrook.qqq.api.utils.ApiQueryFilterUtils;
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
@@ -83,28 +83,18 @@ public class RecordWorkflowUtils
/***************************************************************************
* expects name in inputValues to be "queryFilterJson"
**
***************************************************************************/
public static QQueryFilter getFilterFromInput(Map<String, ?> inputValues) throws QException
{
Object object = inputValues.get("queryFilterJson");
if(object == null)
{
throw (new QException("Could not find filter in input values"));
}
return getFilterFromJsonOrObject(object);
}
/***************************************************************************
* supported input types are: QQueryFilter, String (json), Map.
***************************************************************************/
public static QQueryFilter getFilterFromJsonOrObject(Object object) throws QException
{
try
{
Object object = inputValues.get("queryFilterJson");
if(object == null)
{
throw (new QException("Could not find filter in input values"));
}
if(object instanceof QQueryFilter qQueryFilter)
{
return (qQueryFilter);
@@ -124,7 +114,7 @@ public class RecordWorkflowUtils
}
catch(IOException e)
{
throw new QException("Error getting filter from Object", e);
throw new QException("Error getting filter from input values", e);
}
}
@@ -189,7 +179,7 @@ public class RecordWorkflowUtils
{
WorkflowRevision workflowRevision = context.getWorkflowRevision();
Workflow workflow = context.getWorkflow();
Map<String, QFieldMetaData> tableApiFields = GetTableApiFieldsAction.getTableApiFieldMap(new GetTableApiFieldsInput().withApiName(workflowRevision.getApiName()).withVersion(workflowRevision.getApiVersion()).withTableName(workflow.getTableName()));
Map<String, QFieldMetaData> tableApiFields = GetTableApiFieldsAction.getTableApiFieldMap(new GetTableApiFieldsAction.ApiNameVersionAndTableName(workflowRevision.getApiName(), workflowRevision.getApiVersion(), workflow.getTableName()));
List<String> badRequestMessages = new ArrayList<>();
CountInput countInput = new CountInput(context.getWorkflow().getTableName())
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -77,10 +78,10 @@ public class RunRecordWorkflowExtractStep extends ExtractViaQueryStep
** Make sure associations are fetched (so api records have children!)
*******************************************************************************/
@Override
protected void customizeInputPreQuery(RunBackendStepInput runBackendStepInput, QueryInput queryInput)
protected void customizeInputPreQuery(QueryInput queryInput)
{
super.customizeInputPreQuery(queryInput);
queryInput.setIncludeAssociations(true);
super.customizeInputPreQuery(runBackendStepInput, queryInput);
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -24,21 +25,16 @@ package com.kingsrook.qbits.workflows.implementations.recordworkflows;
import java.io.Serializable;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import com.kingsrook.qbits.workflows.definition.WorkflowStepType;
import com.kingsrook.qbits.workflows.definition.WorkflowsRegistry;
import com.kingsrook.qbits.workflows.implementations.WorkflowStepUtils;
import com.kingsrook.qbits.workflows.model.WorkflowRevision;
import com.kingsrook.qqq.backend.core.actions.values.SearchPossibleValueSourceAction;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceInput;
import com.kingsrook.qqq.backend.core.model.actions.values.SearchPossibleValueSourceOutput;
import com.kingsrook.qqq.backend.core.model.metadata.code.InitializableViaCodeReference;
@@ -132,24 +128,12 @@ public class UpdateInputRecordFieldMetaDataAdjuster implements FormAdjusterInter
{
QTableMetaData table = QContext.getQInstance().getTable(tableName);
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, newValue);
updatedField = fieldAndJoinTable.field().clone();
updatedField.setName("value");
updatedField.setLabel("Value (" + fieldAndJoinTable.field().getLabel() + ")");
}
////////////////////////////////////////////////////////////////////////////////
// check the workflow registry to see if there is help content for this field //
////////////////////////////////////////////////////////////////////////////////
WorkflowsRegistry workflowsRegistry = WorkflowsRegistry.of(QContext.getQInstance());
WorkflowStepType workflowStepType = workflowsRegistry.getWorkflowStepType(UpdateInputRecordFieldStep.NAME);
if(workflowStepType != null)
{
for(QFieldMetaData fieldMetaData : CollectionUtils.nonNullList(workflowStepType.getInputFields()))
if(fieldAndJoinTable != null)
{
if(fieldMetaData.getName().equals("value"))
{
updatedField.setHelpContents(fieldMetaData.getHelpContents());
}
updatedField = fieldAndJoinTable.field().clone();
updatedField.setName("value");
updatedField.setLabel("Value (" + fieldAndJoinTable.field().getLabel() + ")");
}
}
}
@@ -159,8 +143,6 @@ public class UpdateInputRecordFieldMetaDataAdjuster implements FormAdjusterInter
}
}
adjustPossibleValueSourceFilterCriteriaInputs(updatedField.getPossibleValueSourceFilter());
///////////////////////////////////////////////////////////////////////////////////
// build the output object, starting with the new meta-data of the 'value' field //
///////////////////////////////////////////////////////////////////////////////////
@@ -204,43 +186,6 @@ public class UpdateInputRecordFieldMetaDataAdjuster implements FormAdjusterInter
/***************************************************************************
* if the field has a possible-value-source filter on it, and that filter
* uses an input field e.g., ${input.someField} - consider that this value
* *might* be an attribute on the workflow so add an optional
* ??${input.workflow.someField} after it.
***************************************************************************/
static void adjustPossibleValueSourceFilterCriteriaInputs(QQueryFilter filter)
{
if(filter == null)
{
return;
}
for(QFilterCriteria criteria : CollectionUtils.nonNullList(filter.getCriteria()))
{
List<Serializable> values = CollectionUtils.nonNullList(criteria.getValues());
ListIterator<Serializable> valueIterator = values.listIterator();
while(valueIterator.hasNext())
{
Serializable value = valueIterator.next();
if(value instanceof String stringValue && stringValue.startsWith("${input."))
{
String inputFieldName = stringValue.replace("${input.", "").replace("}", "");
String updatedValue = stringValue + "??${input.workflow." + inputFieldName + "}";
valueIterator.set(updatedValue);
}
}
}
for(QQueryFilter subFilter : CollectionUtils.nonNullList(filter.getSubFilters()))
{
adjustPossibleValueSourceFilterCriteriaInputs(subFilter);
}
}
/***************************************************************************
**
***************************************************************************/
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.implementations.recordworkflows;
@@ -59,10 +60,7 @@ import org.json.JSONObject;
/*******************************************************************************
* Workflow step that updates a record with one value in one field
*
* This class includes some public static methods that can be useful for other
* workflow step implementations that have similar functionality.
** workflow step that updates a record with one value in one field
*******************************************************************************/
public class UpdateInputRecordFieldStep extends WorkflowStepType implements WorkflowStepExecutorInterface, WorkflowStepValidatorInterface
{
@@ -103,15 +101,7 @@ public class UpdateInputRecordFieldStep extends WorkflowStepType implements Work
/***************************************************************************
* For a given fieldName, tableName, and api and version (which come from a
* workflowRevision), get QFieldMetaData for that field (if it's in that
* API Version).
*
* @param fieldNameMaybeWithTableNamePrefix - field name - which can be
* prefixed by tableName + "."
* @param tableName table name
* @param workflowRevision from which api name and version will come
* @return optional containing QFieldMetaData if found, or empty if not.
**
***************************************************************************/
public static Optional<QFieldMetaData> getApiField(String fieldNameMaybeWithTableNamePrefix, String tableName, WorkflowRevision workflowRevision) throws QException
{
@@ -131,31 +121,6 @@ public class UpdateInputRecordFieldStep extends WorkflowStepType implements Work
***************************************************************************/
@Override
public String getDynamicStepSummary(Integer workflowId, Map<String, Serializable> inputValues) throws QException
{
QRecord workflowRecord = GetAction.execute(Workflow.TABLE_NAME, workflowId);
String tableName = workflowRecord.getValueString("tableName");
return getStepSummary(inputValues, tableName, false);
}
/***************************************************************************
* Helper for {@link #getDynamicStepSummary(Integer, Map)} or
* {@link #execute(WorkflowStep, Map, WorkflowExecutionContext)} to generate
* a string like "${fieldLabel} will be / was set to ${value}" or "... cleared out"
* where the value will be a translated possible value if applicable.
*
* @param inputValues map of values user assigned to the step. expected to
* include String fieldName and String value.
* @param tableName table that the field should come from. Many times this
* may be the tableName on the workflow - but it could be
* a hard-coded tableName in other implementations of this
* step type.
* @param isPastTense to format the message as "will be" or "was". e.g.,
* for getDynamicStepSummary would be false ("will be")
* and for execute would be true ("was").
***************************************************************************/
public static String getStepSummary(Map<String, Serializable> inputValues, String tableName, boolean isPastTense)
{
String fieldName = ValueUtils.getValueAsString(inputValues.get("fieldName"));
String value = ValueUtils.getValueAsString(inputValues.get("value"));
@@ -172,37 +137,44 @@ public class UpdateInputRecordFieldStep extends WorkflowStepType implements Work
}
}
return getStepSummary(fieldName, tableName, apiName, apiVersion, value, isPastTense);
return getStepSummary(workflowId, fieldName, apiName, apiVersion, value, false);
}
/***************************************************************************
*
**
***************************************************************************/
private static String getStepSummary(String fieldName, String tableName, String apiName, String apiVersion, String value, boolean isPastTense)
private String getStepSummary(Integer workflowId, String fieldName, String apiName, String apiVersion, String value, boolean isPastTense)
{
String fieldLabel = null;
QTableMetaData table = QContext.getQInstance().getTable(tableName);
QTableMetaData table = null;
if(StringUtils.hasContent(fieldName))
{
try
{
WorkflowRevision workflowRevision = new WorkflowRevision().withApiName(apiName).withApiVersion(apiVersion);
if(WorkflowStepUtils.useApi(workflowRevision))
if(workflowId != null)
{
Optional<QFieldMetaData> optionalApiField = getApiField(fieldName, tableName, workflowRevision);
if(optionalApiField.isPresent())
QRecord workflowRecord = GetAction.execute(Workflow.TABLE_NAME, workflowId);
table = QContext.getQInstance().getTable(workflowRecord.getValueString("tableName"));
WorkflowRevision workflowRevision = new WorkflowRevision().withApiName(apiName).withApiVersion(apiVersion);
if(WorkflowStepUtils.useApi(workflowRevision))
{
fieldLabel = optionalApiField.get().getLabel();
Optional<QFieldMetaData> optionalApiField = getApiField(fieldName, table.getName(), workflowRevision);
if(optionalApiField.isPresent())
{
// fieldName = optionalApiField.field().getName(); // hmm?
fieldLabel = optionalApiField.get().getLabel();
}
}
else
{
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, fieldName);
fieldName = fieldAndJoinTable.field().getName();
fieldLabel = fieldAndJoinTable.field().getLabel();
}
}
else
{
FieldAndJoinTable fieldAndJoinTable = FieldAndJoinTable.get(table, fieldName);
fieldName = fieldAndJoinTable.field().getName();
fieldLabel = fieldAndJoinTable.field().getLabel();
}
}
catch(Exception e)
@@ -211,22 +183,15 @@ public class UpdateInputRecordFieldStep extends WorkflowStepType implements Work
}
}
String verb = isPastTense ? "was" : "will be";
if(StringUtils.hasContent(fieldLabel))
{
String verb = isPastTense ? "was" : "will be";
if(StringUtils.hasContent(value))
{
String displayValue = value;
try
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// field names from this PVS are always table.field - but QValueFormatter doesn't expect that - so strip away table. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(fieldName != null && fieldName.startsWith(tableName + "."))
{
fieldName = fieldName.replace(tableName + ".", "");
}
QRecord record = new QRecord().withValue(fieldName, value);
QValueFormatter.setDisplayValuesInRecordsIncludingPossibleValueTranslations(table, List.of(record));
displayValue = record.getDisplayValue(fieldName);
@@ -276,8 +241,7 @@ public class UpdateInputRecordFieldStep extends WorkflowStepType implements Work
context.doesRecordNeedUpdated.set(true);
String tableName = context.getWorkflow().getTableName();
String stepSummary = getStepSummary(fieldName, tableName, context.getWorkflowRevision().getApiName(), context.getWorkflowRevision().getApiVersion(), value, true);
String stepSummary = getStepSummary(context.getWorkflow().getId(), fieldName, context.getWorkflowRevision().getApiName(), context.getWorkflowRevision().getApiVersion(), value, true);
return new WorkflowStepOutput(value, stepSummary);
}
@@ -320,34 +284,12 @@ public class UpdateInputRecordFieldStep extends WorkflowStepType implements Work
@Override
public void validate(WorkflowStep step, Map<String, Serializable> inputValues, QRecord workflowRevision, QRecord workflow, List<String> errors) throws QException
{
validate(workflow.getValueString("tableName"), inputValues, workflowRevision, errors);
}
/***************************************************************************
* implementation logic for {@link #validate(WorkflowStep, Map, QRecord, QRecord, List)}.
* makes sure that, if the workflow uses API, that the field is a valid field
* for the api version being used in the workflow revision.
*
* @param tableName table that the field should come from. Many times this
* may be the tableName on the workflow - but it could be
* a hard-coded tableName in other implementations of this
* step type.
* @param inputValues map of values user assigned to the step. expected to
* include String fieldName.
* @param workflowRevision from which api name and version will come
* @param errors Out param - list of error message strings, which will be
* added to if the input fieldName isn't in the table for the
* api version on the workflowRevision
***************************************************************************/
public static void validate(String tableName, Map<String, Serializable> inputValues, QRecord workflowRevision, List<String> errors) throws QException
{
if(workflowRevision != null && WorkflowStepUtils.useApi(new WorkflowRevision(workflowRevision)))
if(WorkflowStepUtils.useApi(new WorkflowRevision(workflowRevision)))
{
String fieldName = ValueUtils.getValueAsString(inputValues.get("fieldName"));
String apiName = ValueUtils.getValueAsString(workflowRevision.getValueString("apiName"));
String apiVersion = ValueUtils.getValueAsString(workflowRevision.getValueString("apiVersion"));
String tableName = ValueUtils.getValueAsString(workflow.getValueString("tableName"));
Optional<QFieldMetaData> optionalApiField = getApiField(fieldName, tableName, new WorkflowRevision(workflowRevision));
if(optionalApiField.isEmpty())
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.metadata;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.metadata;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.metadata;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.metadata;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.metadata;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.metadata;
@@ -31,7 +32,7 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
*******************************************************************************/
public class WorkflowRevisionViewerWidget extends BaseQSequentialWorkflowWidgetRenderer
{
public static final String NAME = "WorkflowRevisionViewerWidget";
public static final String NAME = "WorkflowRunLogViewer";
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.metadata;
@@ -42,7 +43,7 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
*******************************************************************************/
public class WorkflowRunLogViewerWidget extends BaseQSequentialWorkflowWidgetRenderer
{
public static final String NAME = "WorkflowRunLogViewerWidget";
public static final String NAME = "WorkflowRevisionViewer";
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.metadata;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.metadata;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.metadata;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.metadata;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.metadata;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.metadata;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.model;
@@ -83,7 +84,7 @@ public class WorkflowLink extends QRecordEntity
@QField(isRequired = true)
private Integer fromStepNo;
@QField()
@QField(isRequired = true)
private Integer toStepNo;
@QField(maxLength = 20, valueTooLongBehavior = ValueTooLongBehavior.ERROR)
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.processes;
@@ -1,115 +0,0 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.kingsrook.qbits.workflows.processes;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.google.gson.reflect.TypeToken;
import com.kingsrook.qbits.workflows.definition.OutboundLinkMode;
import com.kingsrook.qbits.workflows.definition.OutboundLinkOption;
import com.kingsrook.qbits.workflows.definition.WorkflowStepType;
import com.kingsrook.qbits.workflows.definition.WorkflowsRegistry;
import com.kingsrook.qqq.backend.core.actions.processes.BackendStep;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
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.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QFunctionInputMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QProcessMetaData;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
/*******************************************************************************
* process to get values that the backend must compute about a workflow step,
* based on input values, e.g., as a user is editing the step in a UI.
*
* Originally this was just the step summary (e.g., workflowStepType.getDynamicStepSummary)
*
* Then, when variable outboundLinkModes were added, also responsible for returning
* the link options to use for a step, based on its input values.
*
*******************************************************************************/
public class GetWorkflowStepDynamicValuesProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
{
public static final String NAME = "getWorkflowStepDynamicValues";
/***************************************************************************
**
***************************************************************************/
@Override
public QProcessMetaData produce(QInstance qInstance) throws QException
{
return new QProcessMetaData()
.withName(NAME)
.withPermissionRules(new QPermissionRules().withLevel(PermissionLevel.NOT_PROTECTED))
.withStep(new QBackendStepMetaData()
.withName("execute")
.withCode(new QCodeReference(getClass()))
.withInputData(new QFunctionInputMetaData()
.withField(new QFieldMetaData("workflowId", QFieldType.INTEGER))
.withField(new QFieldMetaData("workflowStepTypeName", QFieldType.STRING))
.withField(new QFieldMetaData("values", QFieldType.INTEGER)) // JSON
));
}
/***************************************************************************
**
***************************************************************************/
@Override
public void run(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
Integer workflowId = runBackendStepInput.getValueInteger("workflowId");
String workflowStepTypeName = runBackendStepInput.getValueString("workflowStepTypeName");
String values = runBackendStepInput.getValueString("values");
Map<String, Serializable> valueAsMap = ValueUtils.getValueAsMap(values);
WorkflowStepType workflowStepType = WorkflowsRegistry.of(QContext.getQInstance()).getWorkflowStepType(workflowStepTypeName);
runBackendStepOutput.addValue("summary", workflowStepType.getDynamicStepSummary(workflowId, valueAsMap));
if(OutboundLinkMode.VARIABLE.equals(workflowStepType.getOutboundLinkMode()))
{
List<OutboundLinkOption> dynamicOutboundLinkOptions = workflowStepType.getDynamicOutboundLinkOptions(workflowId, valueAsMap);
if(dynamicOutboundLinkOptions != null)
{
ArrayList<OutboundLinkOption> outboundLinkOptions = CollectionUtils.useOrWrap(dynamicOutboundLinkOptions, new TypeToken<>() {});
runBackendStepOutput.addValue("outboundLinkOptions", outboundLinkOptions);
}
}
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.processes;
@@ -47,7 +48,6 @@ import com.kingsrook.qqq.backend.core.utils.ValueUtils;
** process to get a dynamic summary of a workflow step, e.g., as it's being
** edited in a UI.
*******************************************************************************/
@Deprecated(since = "0.115.0 - November 2025 - Replaced by GetWorkflowStepDynamicValuesProcess")
public class GetWorkflowStepSummaryProcess implements BackendStep, MetaDataProducerInterface<QProcessMetaData>
{
public static final String NAME = "getWorkflowStepSummary";
@@ -58,6 +58,7 @@ public class GetWorkflowStepSummaryProcess implements BackendStep, MetaDataProdu
**
***************************************************************************/
@Override
public QProcessMetaData produce(QInstance qInstance) throws QException
{
return new QProcessMetaData()
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.processes;
@@ -40,14 +41,12 @@ import com.kingsrook.qqq.backend.core.exceptions.QException;
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.data.QRecord;
import com.kingsrook.qqq.backend.core.model.helpcontent.HelpContent;
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.code.QCodeReference;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
import com.kingsrook.qqq.backend.core.model.metadata.frontend.QFrontendFieldMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.help.QHelpContent;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.PermissionLevel;
import com.kingsrook.qqq.backend.core.model.metadata.permissions.QPermissionRules;
import com.kingsrook.qqq.backend.core.model.metadata.processes.QBackendStepMetaData;
@@ -117,12 +116,8 @@ public class GetWorkflowTypeDefinitionProcess implements BackendStep, MetaDataPr
throw new QException("Workflow type not found: " + workflowTypeName);
}
////////////////////////////////////////////////////////////////////////////////
// save any help content on current workflow and put back after customization //
////////////////////////////////////////////////////////////////////////////////
Map<String, List<QHelpContent>> helpContent = workflowType.getHelpContent();
workflowType = workflowType.customizeBasedOnWorkflow(workflowRecord);
workflowType.setHelpContent(helpContent);
runBackendStepOutput.addValue("workflowType", workflowType);
////////////////////////////////////////////////////////////////////////////////
@@ -172,24 +167,4 @@ public class GetWorkflowTypeDefinitionProcess implements BackendStep, MetaDataPr
}
}
/***************************************************************************
**
***************************************************************************/
protected Map<String, List<HelpContent>> getWorkflowTypeHelpContent(Map<String, HelpContent> helpContentMap)
{
Map<String, List<HelpContent>> workflowTypeHelpContent = new LinkedHashMap<>();
String[] workflowTypeKeys = new String[] { "workflow:editor;field:workflowName", "workflow:editor;field:versionNo", "workflow:editor;field:apiName", "workflow:editor;field:apiVersion", "workflow:tester;field:workflowTestScenarioId" };
for(String key : workflowTypeKeys)
{
if(helpContentMap.containsKey(key))
{
workflowTypeHelpContent.put(key, List.of(helpContentMap.get(key)));
}
}
return (workflowTypeHelpContent);
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.processes;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.processes;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.processes;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.processes;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.processes;
@@ -28,10 +29,8 @@ import java.util.Map;
import java.util.Objects;
import com.fasterxml.jackson.core.type.TypeReference;
import com.kingsrook.qbits.workflows.definition.WorkflowStepType;
import com.kingsrook.qbits.workflows.definition.WorkflowType;
import com.kingsrook.qbits.workflows.definition.WorkflowsRegistry;
import com.kingsrook.qbits.workflows.execution.WorkflowStepValidatorInterface;
import com.kingsrook.qbits.workflows.execution.WorkflowTypeValidatorInterface;
import com.kingsrook.qbits.workflows.model.Workflow;
import com.kingsrook.qbits.workflows.model.WorkflowLink;
import com.kingsrook.qbits.workflows.model.WorkflowRevision;
@@ -141,38 +140,6 @@ public class StoreNewWorkflowRevisionProcess implements BackendStep, MetaDataPro
throw (new QUserFacingException("Workflow not found by id: " + workflowId));
}
String stepsJSON = runBackendStepInput.getValueString("steps");
if(!StringUtils.hasContent(stepsJSON))
{
throw (new QUserFacingException("Workflow steps input was not provided."));
}
List<WorkflowStep> workflowSteps;
try
{
workflowSteps = JsonUtils.toObject(stepsJSON, new TypeReference<>() {});
}
catch(Exception e)
{
throw (new QUserFacingException("Error parsing workflow steps json: " + e.getMessage(), e));
}
String linksJSON = runBackendStepInput.getValueString("links");
if(!StringUtils.hasContent(linksJSON))
{
throw (new QUserFacingException("Workflow links input was not provided."));
}
List<WorkflowLink> workflowLinks;
try
{
workflowLinks = JsonUtils.toObject(linksJSON, new TypeReference<>() {});
}
catch(Exception e)
{
throw (new QUserFacingException("Error parsing workflow links json: " + e.getMessage(), e));
}
////////////////////////////
// look up next versionNo //
////////////////////////////
@@ -199,15 +166,15 @@ public class StoreNewWorkflowRevisionProcess implements BackendStep, MetaDataPro
// insert the workflow revision //
//////////////////////////////////
String commitMessage = runBackendStepInput.getValueString("commitMessage");
String userName = ObjectUtils.tryElse(() -> QContext.getQSession().getUser().getFullName(), "Unknown");
WorkflowRevision workflowRevision = new WorkflowRevision();
workflowRevision.setWorkflowId(workflowId);
workflowRevision.setVersionNo(versionNo);
workflowRevision.setCommitMessage(StringUtils.hasContent(commitMessage) ? commitMessage : "New workflow revision created by " + userName);
workflowRevision.setCommitMessage(StringUtils.hasContent(commitMessage) ? commitMessage : "New workflow revision created by " + QContext.getQSession().getUser().getFullName());
workflowRevision.setApiName(runBackendStepInput.getValueString("apiName"));
workflowRevision.setApiVersion(runBackendStepInput.getValueString("apiVersion"));
workflowRevision.setAuthor(userName);
workflowRevision.setAuthor(ObjectUtils.tryElse(() -> QContext.getQSession().getUser().getFullName(), "Unknown"));
List<WorkflowStep> workflowSteps = JsonUtils.toObject(runBackendStepInput.getValueString("steps"), new TypeReference<>() {});
if(!workflowSteps.isEmpty())
{
workflowRevision.setStartStepNo(workflowSteps.get(0).getStepNo());
@@ -241,24 +208,19 @@ public class StoreNewWorkflowRevisionProcess implements BackendStep, MetaDataPro
//////////////////////////////////////////////////////////////////////////////////////////////
// store steps (first setting revision id, validating, and putting a fresh summary on each) //
//////////////////////////////////////////////////////////////////////////////////////////////
List<String> errors = new ArrayList<>();
List<String> errorsForThisStep = new ArrayList<>();
List<String> errors = new ArrayList<>();
workflowSteps.forEach(step ->
{
try
{
step.setWorkflowRevisionId(insertedRevisionId);
WorkflowStepType workflowStepType = WorkflowsRegistry.of(QContext.getQInstance()).getWorkflowStepType(step.getWorkflowStepTypeName());
if(workflowStepType == null)
{
throw (new QUserFacingException("Unknown workflow step type " + step.getWorkflowStepTypeName()));
}
WorkflowStepType workflowStepType = WorkflowsRegistry.of(QContext.getQInstance()).getWorkflowStepType(step.getWorkflowStepTypeName());
Map<String, Serializable> inputValues = ValueUtils.getValueAsMap(step.getInputValuesJson());
////////////////////////////////////
// summary - if it fails, :shrug: //
////////////////////////////////////
Map<String, Serializable> inputValues = ValueUtils.getValueAsMap(step.getInputValuesJson());
try
{
step.setSummary(workflowStepType.getDynamicStepSummary(workflowId, inputValues));
@@ -275,37 +237,18 @@ public class StoreNewWorkflowRevisionProcess implements BackendStep, MetaDataPro
if(validatorCodeReference != null)
{
WorkflowStepValidatorInterface validator = QCodeLoader.getAdHoc(WorkflowStepValidatorInterface.class, validatorCodeReference);
validator.validate(step, inputValues, workflowRevisionRecord, workflowRecord, errorsForThisStep);
if(!errorsForThisStep.isEmpty())
{
///////////////////////////////////////
// put Step # in front of all errors //
///////////////////////////////////////
errors.addAll(errorsForThisStep.stream().map(e -> "Step " + step.getStepNo() + ": " + e).toList());
errorsForThisStep.clear();
}
validator.validate(step, inputValues, workflowRevisionRecord, workflowRecord, errors);
}
}
catch(Exception e)
{
errors.add("Error processing Step " + step.getStepNo() + ": " + e.getMessage());
errors.add("Error processing step [" + step.getSummary() + "]: " + e.getMessage());
}
});
////////////////////////////////////////////////////////////
// do workflow-level validation as well, if so configured //
////////////////////////////////////////////////////////////
WorkflowType workflowType = WorkflowsRegistry.of(QContext.getQInstance()).getWorkflowType(workflowRecord.getValueString("workflowTypeName"));
if(workflowType.getValidator() != null)
{
WorkflowTypeValidatorInterface validator = QCodeLoader.getAdHoc(WorkflowTypeValidatorInterface.class, workflowType.getValidator());
validator.validate(workflowRecord, workflowRevisionRecord, workflowSteps, workflowLinks, errors);
}
if(!errors.isEmpty())
{
String message = errors.size() + " validation error" + StringUtils.plural(errors) + " occurred before the workflow could be saved:\n" + StringUtils.join("\n", errors);
throw (new QUserFacingException(message));
throw (new QUserFacingException("Validation errors in steps: " + StringUtils.joinWithCommasAndAnd(errors)));
}
new InsertAction().execute(new InsertInput(WorkflowStep.TABLE_NAME).withRecordEntities(workflowSteps).withTransaction(transaction));
@@ -313,6 +256,7 @@ public class StoreNewWorkflowRevisionProcess implements BackendStep, MetaDataPro
/////////////////
// store links //
/////////////////
List<WorkflowLink> workflowLinks = JsonUtils.toObject(runBackendStepInput.getValueString("links"), new TypeReference<>() {});
workflowLinks.forEach(link -> link.setWorkflowRevisionId(insertedRevisionId));
new InsertAction().execute(new InsertInput(WorkflowLink.TABLE_NAME).withRecordEntities(workflowLinks).withTransaction(transaction));
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.processes;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.tables;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.tables;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.tables;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.tables;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.tracing;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.tracing;
@@ -5,17 +5,18 @@
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 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.
*
* https://www.apache.org/licenses/LICENSE-2.0
* 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.
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* 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.qbits.workflows.tracing;
Loaded 100 of 119 files, more files were not shown because too many files have changed in this diff. Show more