Compare commits

..
4 Commits
Author SHA1 Message Date
forge-recovery 7885aa235f ci: enroll shared Gitea lifecycle
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / head-profile (push) Skipped
QRun-IO/ci-policy: ci / application-template-0 (push) Skipped
QRun-IO/ci-policy: ci / gradle-1 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (push) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (push) Skipped
QRun-IO/ci-policy: ci / node-8 (push) Skipped
QRun-IO/ci-policy: ci / node-9 (push) Skipped
QRun-IO/ci-policy: ci / node-10 (push) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (push) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (push) Skipped
QRun-IO/ci-policy: ci / source-config-13 (push) Skipped
QRun-IO/ci-policy: ci / source-config-14 (push) Skipped
QRun-IO/ci-policy: ci / android-0 (push) Skipped
QRun-IO/ci-policy: ci / original-business (push) Skipped
QRun-IO/ci-policy: ci / original-mcp (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QRun-IO/ci-policy: native / fork-deny (push) Skipped
QRun-IO/ci-policy: native / dotnet-0 (push) Skipped
Shared Java Maven CI / secrets (push) Successful in 23s
QRun-IO/ci-policy: Shared Java Maven CI / secrets (push) Successful in 26s
QRun-IO/ci-policy: ci / repository-policy (push) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (push) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (push) Skipped
QRun-IO/ci-policy: native / native-result (push) Successful in 4s
QRun-IO/ci-policy: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
Shared Java Maven CI / verify (push) Successful in 1m41s
Shared Java Maven CI / gate (push) Successful in 9s
qrun-release / release (push) Successful in 21m23s
QRun-IO/ci-policy: Shared Java Maven CI / verify (push) Successful in 59s
QRun-IO/ci-policy: Shared Java Maven CI / gate (push) Successful in 9s
QRun-IO/ci-policy: ci / java-maven-6 (push) Successful in 21m51s
QRun-IO/ci-policy: ci / ci-result (push) Successful in 6s
Shared QRun source version / version (push) Failing after 32m54s
qrun-release / postpublish (push) Failing after 32m54s
2026-11-03 17:34:52 +00:00
forge-recoveryandCodex 0ef5b01ed3 ci: enroll shared Gitea lifecycle
QRun-IO/ci-policy: ci / fork-guard (pull_request) Skipped
QRun-IO/ci-policy: ci / head-profile (pull_request) Skipped
QRun-IO/ci-policy: ci / application-template-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / gradle-1 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-9 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-10 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-11 (pull_request) Skipped
QRun-IO/ci-policy: ci / repository-policy-12 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-13 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-14 (pull_request) Skipped
QRun-IO/ci-policy: ci / android-0 (pull_request) Skipped
QRun-IO/ci-policy: ci / original-business (pull_request) Skipped
QRun-IO/ci-policy: ci / original-mcp (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: native / fork-deny (pull_request) Skipped
QRun-IO/ci-policy: native / dotnet-0 (pull_request) Skipped
Shared Java Maven CI / verify (push) Successful in 19s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 4s
Shared Java Maven CI / secrets (push) Successful in 18s
QRun-IO/ci-policy: Shared Java Maven CI / secrets (pull_request) Successful in 19s
QRun-IO/ci-policy: ci / repository-policy (pull_request) Skipped
QRun-IO/ci-policy: ci / java-core-2-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-docker-3-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6-head (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (pull_request) Skipped
Shared Java Maven CI / gate (push) Successful in 13s
qrun-release / release (push) Successful in 36s
qrun-release / postpublish (push) Skipped
QRun-IO/ci-policy: Shared Java Maven CI / verify (pull_request) Successful in 1m14s
QRun-IO/ci-policy: Shared Java Maven CI / gate (pull_request) Successful in 13s
QRun-IO/ci-policy: ci / java-maven-6 (pull_request) Successful in 1m47s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 5s
Co-Authored-By: Codex <noreply@openai.com>

Co-Authored-By: Codex <noreply@openai.com>
2026-11-03 17:05:33 +00:00
james.maesandCodex a7d06d3c66 ci: enroll in the shared QQQ orb (#20)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 19:55:12 -05:00
james.maesandCodex c42a361317 ci: use shared orb pipelines and source scanning (#16)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 10:50:54 -05:00
36 changed files with 257 additions and 2235 deletions

No files matched your search

-1
View File
@@ -1 +0,0 @@
Acceptance: a fresh protected-branch build must publish through the scheduled trusted publisher and resolve from an empty Maven cache.
-3
View File
@@ -1,3 +0,0 @@
Organization-scoped CI qualification. Workflow implementation lives in platform-ci; enrollment lives in ci-policy.
Shared CI tooling visibility approved and public; qualify public consumer access.
+1 -1
View File
@@ -11,4 +11,4 @@ workflows:
branches: branches:
ignore: automation/qbit-rc-pending ignore: automation/qbit-rc-pending
tags: tags:
only: /^v\d+\.\d+\.\d+$/ 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
+24
View File
@@ -0,0 +1,24 @@
# Standard QRun release caller (v2, thin contract): every push and manual dispatch delegates to the protected qrun-release-v1 channel. Do not edit per repository.
name: qrun-release
on:
push: {}
workflow_dispatch: {}
permissions:
contents: read
actions: read
jobs:
release:
uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/java-maven.yaml@qrun-release-v1
with:
runner: linux-small
browsers: true
browser-runner: browser-build
postpublish:
if: ${{ needs.release.outputs.capture == 'true' }}
needs: release
permissions:
contents: write
actions: write
uses: self:KofTwentyTwo/platform-ci/.gitea/workflows/qrun-source-version.yaml@qrun-release-v1
with:
runner: linux-small
-1
View File
@@ -1 +0,0 @@
* @KofTwentyTwo
-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 -4
View File
@@ -5,12 +5,12 @@ java_version: '21'
browsers: true browsers: true
api_checks: false api_checks: false
docs_only: false docs_only: false
release_on_main: false release_on_main: true
release_tag_policy: stable release_tag_policy: v-prefix
test_branch_policy: non-release test_branch_policy: feature-no-hotfix
publish: publish:
snapshot: true snapshot: true
release_candidate: true release_candidate: true
release: true release: true
hotfix: true
asciidoc: false asciidoc: false
release_completion: true
-22
View File
@@ -1,22 +0,0 @@
# qbit-standard-process-trace
## Knowledge base
A reviewed dossier for this repo lives in the second-brain vault:
- Hub: `$SECOND_BRAIN_VAULT/knowledge/qqq/qqq-hub.md` — map of the whole QQQ knowledge base
- Dossier: `$SECOND_BRAIN_VAULT/knowledge/qqq/repos/qbit-standard-process-trace.md`
(purpose, API surface, data model, QBit contract usage, licensing state, v4.0 impact)
Reviewed at commit `0f2ec0339048` (branch `develop`, 2026-07-04).
Key facts worth knowing before editing:
- `main` was merged back into `develop` for QRun-IO/qqq#766, so `develop` has main's
Java 21 + Apache-2.0 LICENSE/NOTICE and the backend-activity-stats feature. Current
first-party pom, source-header and README license declarations also use Apache-2.0.
- The qqq version comes only from `qbit-build-parent` 2.0.0 (qqq 4.0.0); do not re-add a
`qqq-bom-pom` import. `mvn -B verify -Pqqq-snapshot` checks against qqq `4.1.0-SNAPSHOT`
(override with `-Dqqq.snapshot.version=...`).
- CHANGELOG.md text is copy-pasted from a different QBit; README code examples have drift
(nonexistent `producer.withTableMetaDataCustomizer`, table-name typos).
+2 -2
View File
@@ -24,7 +24,7 @@ This QBit provides automatic tracing for QQQ processes. Every execution is logge
### Prerequisites ### Prerequisites
- QQQ application (v4.1.0+) - QQQ application (v0.35.0+)
- Database backend configured - Database backend configured
### Installation ### Installation
@@ -132,4 +132,4 @@ Stable and production-ready.
## License ## License
Apache-2.0 - See [LICENSE](LICENSE) and [NOTICE](NOTICE). Proprietary - QRun.IO
+26 -23
View File
@@ -6,30 +6,27 @@
~ contact@kingsrook.com ~ contact@kingsrook.com
~ https://github.com/Kingsrook/ ~ https://github.com/Kingsrook/
~ ~
~ Licensed under the Apache License, Version 2.0 (the "License"); ~ This program is free software: you can redistribute it and/or modify
~ you may not use this file except in compliance with the License. ~ it under the terms of the GNU Affero General Public License as
~ You may obtain a copy of the License at ~ 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 ~ You should have received a copy of the GNU Affero General Public License
~ distributed under the License is distributed on an "AS IS" BASIS, ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
~ 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.
--> -->
<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"> <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">
<modelVersion>4.0.0</modelVersion>
<properties>
<revision>0.3.0-SNAPSHOT</revision>
<qqq.version>4.1.0-RC.26</qqq.version>
</properties>
<parent> <parent>
<groupId>com.kingsrook</groupId> <groupId>com.kingsrook</groupId>
<artifactId>qbit-build-parent</artifactId> <artifactId>qbit-build-parent</artifactId>
<version>2.2.1</version> <version>1.5.1</version>
</parent> </parent>
<groupId>com.kingsrook.qbits</groupId> <groupId>com.kingsrook.qbits</groupId>
@@ -41,12 +38,10 @@
<description>Standard process tracing module for the QBit framework, providing process trace tables and StandardProcessTracer implementation</description> <description>Standard process tracing module for the QBit framework, providing process trace tables and StandardProcessTracer implementation</description>
<url>https://github.com/Kingsrook/qbit-standard-process-trace</url> <url>https://github.com/Kingsrook/qbit-standard-process-trace</url>
<modelVersion>4.0.0</modelVersion>
<licenses> <licenses>
<license> <license>
<name>Apache License, Version 2.0</name> <name>GNU Affero General Public License v3.0</name>
<url>https://www.apache.org/licenses/LICENSE-2.0</url> <url>https://www.gnu.org/licenses/agpl-3.0.html</url>
<distribution>repo</distribution> <distribution>repo</distribution>
</license> </license>
</licenses> </licenses>
@@ -68,20 +63,24 @@
<tag>HEAD</tag> <tag>HEAD</tag>
</scm> </scm>
<properties>
<revision>0.2.0</revision>
</properties>
<dependencyManagement> <dependencyManagement>
<dependencies> <dependencies>
<!-- Published QQQ 4.1 core (minimum QQQ 4.1), imported ahead of the parent's BOM. -->
<dependency> <dependency>
<groupId>com.kingsrook.qqq</groupId> <groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-bom-pom</artifactId> <artifactId>qqq-bom-pom</artifactId>
<version>${qqq.version}</version> <version>0.27.9</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.junit</groupId> <groupId>org.junit</groupId>
<artifactId>junit-bom</artifactId> <artifactId>junit-bom</artifactId>
<version>6.0.2</version> <version>5.10.0</version>
<type>pom</type> <type>pom</type>
<scope>import</scope> <scope>import</scope>
</dependency> </dependency>
@@ -93,6 +92,10 @@
<groupId>com.kingsrook.qqq</groupId> <groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-core</artifactId> <artifactId>qqq-backend-core</artifactId>
</dependency> </dependency>
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-backend-module-rdbms</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.junit.jupiter</groupId> <groupId>org.junit.jupiter</groupId>
@@ -102,7 +105,7 @@
<dependency> <dependency>
<groupId>org.assertj</groupId> <groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId> <artifactId>assertj-core</artifactId>
<version>3.27.7</version> <version>3.27.3</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
@@ -5,41 +5,33 @@
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU Affero General Public License as
* You may obtain a copy of the License at * 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 * You should have received a copy of the GNU Affero General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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.standardprocesstrace; package com.kingsrook.qbits.standardprocesstrace;
import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitConfig;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
/******************************************************************************* /*******************************************************************************
* Configuration data for this qbit. ** ad-hoc Configuration data for this qbit.
* ** TODO - when qbit configs become formalized in qqq, adapt this qbit to that interface.
*******************************************************************************/ *******************************************************************************/
public class StandardProcessTraceQBitConfig implements QBitConfig public class StandardProcessTraceQBitConfig
{ {
private MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer;
private String userIdPossibleValueSourceName; private String userIdPossibleValueSourceName;
private String userTableName; private String userTableName;
private String userIdReferenceFieldName; private String userIdReferenceFieldName;
private boolean includeBackendActivityStats = false;
/******************************************************************************* /*******************************************************************************
@@ -133,82 +125,4 @@ public class StandardProcessTraceQBitConfig implements QBitConfig
} }
/*******************************************************************************
* Getter for includeBackendActivityStats
* @see #withIncludeBackendActivityStats(boolean)
*******************************************************************************/
public boolean getIncludeBackendActivityStats()
{
return (this.includeBackendActivityStats);
}
/*******************************************************************************
* Setter for includeBackendActivityStats
* @see #withIncludeBackendActivityStats(boolean)
*******************************************************************************/
public void setIncludeBackendActivityStats(boolean includeBackendActivityStats)
{
this.includeBackendActivityStats = includeBackendActivityStats;
}
/*******************************************************************************
* Fluent setter for includeBackendActivityStats
*
* @param includeBackendActivityStats
* indicate whether or not the ProcessTraceBackendActivityStats table should be
* included in the QInstance (and records should be built in that table under
* processTrace records).
* <p>Note that using this feature requires the QQQ QueryStatManager to be active
* in the application server.</p>
* @return this
*******************************************************************************/
public StandardProcessTraceQBitConfig withIncludeBackendActivityStats(boolean includeBackendActivityStats)
{
this.includeBackendActivityStats = includeBackendActivityStats;
return (this);
}
/*******************************************************************************
* Getter for tableMetaDataCustomizer
* @see #withTableMetaDataCustomizer(MetaDataCustomizerInterface)
*******************************************************************************/
public MetaDataCustomizerInterface<QTableMetaData> getTableMetaDataCustomizer()
{
return (this.tableMetaDataCustomizer);
}
/*******************************************************************************
* Setter for tableMetaDataCustomizer
* @see #withTableMetaDataCustomizer(MetaDataCustomizerInterface)
*******************************************************************************/
public void setTableMetaDataCustomizer(MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer)
{
this.tableMetaDataCustomizer = tableMetaDataCustomizer;
}
/*******************************************************************************
* Fluent setter for tableMetaDataCustomizer
*
* @param tableMetaDataCustomizer
* table meta data customizer to be applied to all tables produced by this qbit.
* @return this
*******************************************************************************/
public StandardProcessTraceQBitConfig withTableMetaDataCustomizer(MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer)
{
this.tableMetaDataCustomizer = tableMetaDataCustomizer;
return (this);
}
} }
@@ -5,32 +5,33 @@
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU Affero General Public License as
* You may obtain a copy of the License at * 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 * You should have received a copy of the GNU Affero General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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.standardprocesstrace; package com.kingsrook.qbits.standardprocesstrace;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTrace; import com.kingsrook.qbits.standardprocesstrace.model.ProcessTrace;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceBackendActivityStats;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceSummaryLine; import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceSummaryLine;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceSummaryLineRecordInt; import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceSummaryLineRecordInt;
import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerHelper;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput; import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection; import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaData; import com.kingsrook.qqq.backend.core.model.metadata.producers.MetaDataCustomizerInterface;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitMetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData; import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils;
@@ -38,9 +39,10 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
/******************************************************************************* /*******************************************************************************
** **
*******************************************************************************/ *******************************************************************************/
public class StandardProcessTraceQBitProducer implements QBitMetaDataProducer<StandardProcessTraceQBitConfig> public class StandardProcessTraceQBitProducer implements MetaDataProducerInterface<MetaDataProducerMultiOutput>
{ {
private StandardProcessTraceQBitConfig standardProcessTraceQBitConfig; private StandardProcessTraceQBitConfig standardProcessTraceQBitConfig;
private MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer;
@@ -49,71 +51,35 @@ public class StandardProcessTraceQBitProducer implements QBitMetaDataProducer<St
***************************************************************************/ ***************************************************************************/
public static QAppSection getAppSection(QInstance qInstance) public static QAppSection getAppSection(QInstance qInstance)
{ {
QBitMetaData qBitMetaData = qInstance.getQBits().get(new StandardProcessTraceQBitProducer().getQBitMetaData().getName()); return (new QAppSection().withName("processTrace")
QAppSection section = new QAppSection().withName("processTrace")
.withTable(ProcessTrace.TABLE_NAME) .withTable(ProcessTrace.TABLE_NAME)
.withTable(ProcessTraceSummaryLine.TABLE_NAME) .withTable(ProcessTraceSummaryLine.TABLE_NAME)
.withTable(ProcessTraceSummaryLineRecordInt.TABLE_NAME); .withTable(ProcessTraceSummaryLineRecordInt.TABLE_NAME));
}
if(qBitMetaData != null && qBitMetaData.getConfig() instanceof StandardProcessTraceQBitConfig config)
/***************************************************************************
**
***************************************************************************/
@Override
public MetaDataProducerMultiOutput produce(QInstance qInstance) throws QException
{
MetaDataProducerHelper.processAllMetaDataProducersInPackage(qInstance, ProcessTrace.class.getPackageName(), tableMetaDataCustomizer);
MetaDataProducerHelper.processAllMetaDataProducersInPackage(qInstance, getClass().getPackageName() + ".metadata", tableMetaDataCustomizer);
//////////////////////////////////////////////////////////////////////////////////////
// set the possible value source on the processTrace.userId field, if so configured //
//////////////////////////////////////////////////////////////////////////////////////
if(standardProcessTraceQBitConfig != null && StringUtils.hasContent(standardProcessTraceQBitConfig.getUserIdPossibleValueSourceName()))
{ {
if(config.getIncludeBackendActivityStats()) qInstance.getTable(ProcessTrace.TABLE_NAME).getField("userId").setPossibleValueSourceName(standardProcessTraceQBitConfig.getUserIdPossibleValueSourceName());
{
section.withTable(ProcessTraceBackendActivityStats.TABLE_NAME);
}
} }
return (section); ////////////////////////////////////////////////////////////////////////////////////////////////////
} // we actually called MetaDataProducerHelper ourselves, so, don't have anything to return here... //
////////////////////////////////////////////////////////////////////////////////////////////////////
return new MetaDataProducerMultiOutput();
/***************************************************************************
*
***************************************************************************/
@Override
public StandardProcessTraceQBitConfig getQBitConfig()
{
return (standardProcessTraceQBitConfig);
}
/***************************************************************************
*
***************************************************************************/
@Override
public QBitMetaData getQBitMetaData()
{
QBitMetaData qBitMetaData = new QBitMetaData()
.withGroupId("com.kingsrook.qbits")
.withArtifactId("standard-process-trace")
.withVersion("0.1.3")
.withNamespace(getNamespace())
.withConfig(getQBitConfig());
return qBitMetaData;
}
/***************************************************************************
*
***************************************************************************/
@Override
public void postProduceActions(MetaDataProducerMultiOutput metaDataProducerMultiOutput, QInstance qInstance) throws QException
{
if(standardProcessTraceQBitConfig != null)
{
//////////////////////////////////////////////////////////////////////////////////////
// set the possible value source on the processTrace.userId field, if so configured //
//////////////////////////////////////////////////////////////////////////////////////
if(StringUtils.hasContent(standardProcessTraceQBitConfig.getUserIdPossibleValueSourceName()))
{
metaDataProducerMultiOutput.get(QTableMetaData.class, ProcessTrace.TABLE_NAME).getField("userId").setPossibleValueSourceName(standardProcessTraceQBitConfig.getUserIdPossibleValueSourceName());
}
}
} }
@@ -147,4 +113,35 @@ public class StandardProcessTraceQBitProducer implements QBitMetaDataProducer<St
return (this); return (this);
} }
/*******************************************************************************
** Getter for tableMetaDataCustomizer
*******************************************************************************/
public MetaDataCustomizerInterface<QTableMetaData> getTableMetaDataCustomizer()
{
return (this.tableMetaDataCustomizer);
}
/*******************************************************************************
** Setter for tableMetaDataCustomizer
*******************************************************************************/
public void setTableMetaDataCustomizer(MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer)
{
this.tableMetaDataCustomizer = tableMetaDataCustomizer;
}
/*******************************************************************************
** Fluent setter for tableMetaDataCustomizer
*******************************************************************************/
public StandardProcessTraceQBitProducer withTableMetaDataCustomizer(MetaDataCustomizerInterface<QTableMetaData> tableMetaDataCustomizer)
{
this.tableMetaDataCustomizer = tableMetaDataCustomizer;
return (this);
}
} }
@@ -1,61 +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.standardprocesstrace.metadata;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceBackendActivityStatsAction;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.QPossibleValueSource;
/*******************************************************************************
* Meta Data Producer for ProcessTraceBackendActivityStatsAction
* Note: not done via annotations, so it can be disabled via config
*******************************************************************************/
public class ProcessTraceBackendActivityStatsActionMetaDataProducer extends MetaDataProducer<QPossibleValueSource>
{
public static final String NAME = ProcessTraceBackendActivityStatsAction.NAME;
/***************************************************************************
*
***************************************************************************/
@Override
public boolean isEnabled()
{
return (new ProcessTraceBackendActivityStatsMetaDataProducer().isEnabled());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public QPossibleValueSource produce(QInstance qInstance) throws QException
{
return (QPossibleValueSource.newForEnum(NAME, ProcessTraceBackendActivityStatsAction.values()));
}
}
@@ -1,92 +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.standardprocesstrace.metadata;
import java.util.List;
import com.kingsrook.qbits.standardprocesstrace.StandardProcessTraceQBitConfig;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTrace;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceBackendActivityStats;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitConfig;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitProductionContext;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.ExposedJoin;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.tables.SectionFactory;
/*******************************************************************************
* Meta Data Producer for ProcessTraceBackendActivityStats
* Note: not done via annotations, so it can be disabled via config
*******************************************************************************/
public class ProcessTraceBackendActivityStatsMetaDataProducer extends MetaDataProducer<QTableMetaData>
{
/***************************************************************************
*
***************************************************************************/
@Override
public boolean isEnabled()
{
QBitConfig qBitConfig = QBitProductionContext.peekQBitConfig();
if(qBitConfig instanceof StandardProcessTraceQBitConfig config)
{
if(!config.getIncludeBackendActivityStats())
{
return (false);
}
}
return (true);
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public QTableMetaData produce(QInstance qInstance) throws QException
{
QTableMetaData table = new QTableMetaData()
.withName(ProcessTraceBackendActivityStats.TABLE_NAME)
.withIcon(new QIcon().withName("query_stats"))
.withRecordLabelFormat("%s %s %s %s")
.withRecordLabelFields("processTraceId", "qqqBackendId", "qqqTableId", "actionName")
.withPrimaryKeyField("id")
.withFieldsFromEntity(ProcessTraceBackendActivityStats.class)
.withSection(SectionFactory.defaultT1("id", "processTraceId", "qqqBackendId", "qqqTableId", "actionName"))
.withSection(SectionFactory.defaultT2("callCount", "recordCount", "runtimeMillis"))
.withExposedJoin(new ExposedJoin().withLabel("Process Trace").withJoinPath(List.of(ProcessTraceJoinBackendActivityStatsMetaDataProducer.NAME)).withJoinTable(ProcessTrace.TABLE_NAME));
table.withoutCapabilities(Capability.allWriteCapabilities());
QInstanceEnricher.setInferredFieldBackendNames(table);
return (table);
}
}
@@ -1,70 +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.standardprocesstrace.metadata;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTrace;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceBackendActivityStats;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinOn;
import com.kingsrook.qqq.backend.core.model.metadata.joins.JoinType;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
/*******************************************************************************
* Meta Data Producer for ProcessTraceJoinBackendActivityStats
* Note: not done via annotations, so it can be disabled via config
*******************************************************************************/
public class ProcessTraceJoinBackendActivityStatsMetaDataProducer extends MetaDataProducer<QJoinMetaData>
{
public static final String NAME = "ProcessTraceJoinBackendActivityStats";
/***************************************************************************
*
***************************************************************************/
@Override
public boolean isEnabled()
{
return (new ProcessTraceBackendActivityStatsMetaDataProducer().isEnabled());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public QJoinMetaData produce(QInstance qInstance) throws QException
{
return (new QJoinMetaData()
.withName(NAME)
.withLeftTable(ProcessTrace.TABLE_NAME)
.withRightTable(ProcessTraceBackendActivityStats.TABLE_NAME)
.withType(JoinType.ONE_TO_MANY)
.withJoinOn(new JoinOn("id", "processTraceId"))
);
}
}
@@ -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.standardprocesstrace.metadata;
import com.kingsrook.qqq.backend.core.actions.dashboard.widgets.ChildRecordListRenderer;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducer;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.dashboard.QWidgetMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.joins.QJoinMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.qbits.QBitProductionContext;
/*******************************************************************************
* Meta Data Producer for ProcessTraceJoinBackendActivityStats
* Note: not done via annotations, so it can be disabled via config
*******************************************************************************/
public class ProcessTraceJoinBackendActivityStatsWidgetMetaDataProducer extends MetaDataProducer<QWidgetMetaData>
{
public static final String NAME = "ProcessTraceJoinBackendActivityStatsWidget";
/***************************************************************************
*
***************************************************************************/
@Override
public boolean isEnabled()
{
return (new ProcessTraceBackendActivityStatsMetaDataProducer().isEnabled());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public QWidgetMetaData produce(QInstance qInstance) throws QException
{
MetaDataProducerMultiOutput metaDataProducerMultiOutput = QBitProductionContext.peekMetaDataProducerMultiOutput();
QJoinMetaData joinMetaData = metaDataProducerMultiOutput.get(QJoinMetaData.class, ProcessTraceJoinBackendActivityStatsMetaDataProducer.NAME);
return ChildRecordListRenderer.widgetMetaDataBuilder(joinMetaData)
.withName(NAME)
.withLabel("Backend Activity Stats")
.withMaxRows(250)
.getWidgetMetaData();
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU Affero General Public License as
* You may obtain a copy of the License at * 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 * You should have received a copy of the GNU Affero General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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.standardprocesstrace.metadata; package com.kingsrook.qbits.standardprocesstrace.metadata;
@@ -5,17 +5,18 @@
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU Affero General Public License as
* You may obtain a copy of the License at * 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 * You should have received a copy of the GNU Affero General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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.standardprocesstrace.metadata; package com.kingsrook.qbits.standardprocesstrace.metadata;
@@ -5,17 +5,18 @@
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU Affero General Public License as
* You may obtain a copy of the License at * 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 * You should have received a copy of the GNU Affero General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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.standardprocesstrace.model; package com.kingsrook.qbits.standardprocesstrace.model;
@@ -23,9 +24,6 @@ package com.kingsrook.qbits.standardprocesstrace.model;
import java.time.Instant; import java.time.Instant;
import java.util.List; import java.util.List;
import com.kingsrook.qbits.standardprocesstrace.metadata.ProcessTraceBackendActivityStatsMetaDataProducer;
import com.kingsrook.qbits.standardprocesstrace.metadata.ProcessTraceJoinBackendActivityStatsMetaDataProducer;
import com.kingsrook.qbits.standardprocesstrace.metadata.ProcessTraceJoinBackendActivityStatsWidgetMetaDataProducer;
import com.kingsrook.qbits.standardprocesstrace.metadata.ProcessTraceJoinKeyRecordQQQTableMetaDataProducer; import com.kingsrook.qbits.standardprocesstrace.metadata.ProcessTraceJoinKeyRecordQQQTableMetaDataProducer;
import com.kingsrook.qbits.standardprocesstrace.metadata.ProcessTraceJoinQQQProcessMetaDataProducer; import com.kingsrook.qbits.standardprocesstrace.metadata.ProcessTraceJoinQQQProcessMetaDataProducer;
import com.kingsrook.qbits.standardprocesstrace.utils.ProcessTraceTableCustomizer; import com.kingsrook.qbits.standardprocesstrace.utils.ProcessTraceTableCustomizer;
@@ -68,7 +66,7 @@ import com.kingsrook.qqq.backend.core.model.tables.QQQTable;
childTableEntityClass = ProcessTraceSummaryLine.class, childTableEntityClass = ProcessTraceSummaryLine.class,
joinFieldName = "processTraceSummaryLineId", joinFieldName = "processTraceSummaryLineId",
childJoin = @ChildJoin(enabled = true), childJoin = @ChildJoin(enabled = true),
childRecordListWidget = @ChildRecordListWidget(label = "Summary Lines", enabled = true, maxRows = 250)), childRecordListWidget = @ChildRecordListWidget(label = "Summary Lines", enabled = true, maxRows = 250))
} }
) )
public class ProcessTrace extends QRecordEntity public class ProcessTrace extends QRecordEntity
@@ -89,7 +87,7 @@ public class ProcessTrace extends QRecordEntity
@Override @Override
public QTableMetaData customizeMetaData(QInstance qInstance, QTableMetaData table) throws QException public QTableMetaData customizeMetaData(QInstance qInstance, QTableMetaData table) throws QException
{ {
String summaryLinesChildJoinName = QJoinMetaData.makeInferredJoinName(TABLE_NAME, ProcessTraceSummaryLine.TABLE_NAME); String childJoinName = QJoinMetaData.makeInferredJoinName(TABLE_NAME, ProcessTraceSummaryLine.TABLE_NAME);
table table
.withUniqueKey(new UniqueKey("processUUID")) .withUniqueKey(new UniqueKey("processUUID"))
@@ -98,25 +96,14 @@ public class ProcessTrace extends QRecordEntity
.withRecordLabelFields("qqqProcessId", "processUUID") .withRecordLabelFields("qqqProcessId", "processUUID")
.withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "qqqProcessId", "processUUID", "userId"))) .withSection(new QFieldSection("identity", new QIcon().withName("badge"), Tier.T1, List.of("id", "qqqProcessId", "processUUID", "userId")))
.withSection(new QFieldSection("data", new QIcon().withName("text_snippet"), Tier.T2, List.of("startTimestamp", "endTimestamp", "runtimeMillis", "keyRecordQqqTableId", "keyRecordId", "recordCount", "exceptionMessage"))) .withSection(new QFieldSection("data", new QIcon().withName("text_snippet"), Tier.T2, List.of("startTimestamp", "endTimestamp", "runtimeMillis", "keyRecordQqqTableId", "keyRecordId", "recordCount", "exceptionMessage")))
.withSection(new QFieldSection("summaryLines", new QIcon().withName("horizontal_rule"), Tier.T2).withWidgetName(summaryLinesChildJoinName)) .withSection(new QFieldSection("summaryLines", new QIcon().withName("horizontal_rule"), Tier.T2).withWidgetName(childJoinName))
.withExposedJoin(new ExposedJoin().withLabel("Summary Lines").withJoinPath(List.of(summaryLinesChildJoinName)).withJoinTable(ProcessTraceSummaryLine.TABLE_NAME)) .withExposedJoin(new ExposedJoin().withLabel("Summary Lines").withJoinPath(List.of(childJoinName)).withJoinTable(ProcessTraceSummaryLine.TABLE_NAME))
.withExposedJoin(new ExposedJoin().withLabel("Key Record Table").withJoinPath(List.of(ProcessTraceJoinKeyRecordQQQTableMetaDataProducer.NAME)).withJoinTable(QQQTable.TABLE_NAME)) .withExposedJoin(new ExposedJoin().withLabel("Key Record Table").withJoinPath(List.of(ProcessTraceJoinKeyRecordQQQTableMetaDataProducer.NAME)).withJoinTable(QQQTable.TABLE_NAME))
.withExposedJoin(new ExposedJoin().withLabel("Process").withJoinPath(List.of(ProcessTraceJoinQQQProcessMetaDataProducer.NAME)).withJoinTable(QQQProcess.TABLE_NAME)); .withExposedJoin(new ExposedJoin().withLabel("Process").withJoinPath(List.of(ProcessTraceJoinQQQProcessMetaDataProducer.NAME)).withJoinTable(QQQProcess.TABLE_NAME));
table.withCustomizer(TableCustomizers.POST_QUERY_RECORD, new QCodeReference(ProcessTraceTableCustomizer.class)); table.withCustomizer(TableCustomizers.POST_QUERY_RECORD, new QCodeReference(ProcessTraceTableCustomizer.class));
table.getField("keyRecordId").withFieldAdornment(new FieldAdornment(AdornmentType.LINK).withValue(AdornmentType.LinkValues.TO_RECORD_FROM_TABLE_DYNAMIC, true)); table.getField("keyRecordId").withFieldAdornment(new FieldAdornment(AdornmentType.LINK).withValue(AdornmentType.LinkValues.TO_RECORD_FROM_TABLE_DYNAMIC, true));
if(new ProcessTraceBackendActivityStatsMetaDataProducer().isEnabled())
{
table.getSections().add(new QFieldSection("backendActivityStats", new QIcon().withName("query_stats"), Tier.T2)
.withWidgetName(ProcessTraceJoinBackendActivityStatsWidgetMetaDataProducer.NAME));
table.withExposedJoin(new ExposedJoin()
.withLabel("Backend Activity Stats")
.withJoinPath(List.of(ProcessTraceJoinBackendActivityStatsMetaDataProducer.NAME))
.withJoinTable(ProcessTraceBackendActivityStats.TABLE_NAME));
}
return (table); return (table);
} }
} }
@@ -1,348 +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.standardprocesstrace.model;
import com.kingsrook.qqq.backend.core.model.backends.QQQBackend;
import com.kingsrook.qqq.backend.core.model.data.QField;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
import com.kingsrook.qqq.backend.core.model.metadata.fields.DisplayFormat;
import com.kingsrook.qqq.backend.core.model.tables.QQQTable;
/*******************************************************************************
* QRecord Entity for ProcessTraceBackendActivityStats table
* Note: not using meta-data producing annotations, so it can be disabled via config
*******************************************************************************/
public class ProcessTraceBackendActivityStats extends QRecordEntity
{
public static final String TABLE_NAME = "processTraceBackendActivityStats";
@QField(isEditable = false, isPrimaryKey = true)
private Long id;
@QField(possibleValueSourceName = ProcessTrace.TABLE_NAME)
private Long processTraceId;
@QField(label = "Backend", possibleValueSourceName = QQQBackend.TABLE_NAME)
private Integer qqqBackendId;
@QField(label = "Table", possibleValueSourceName = QQQTable.TABLE_NAME)
private Integer qqqTableId;
@QField(label = "Action", possibleValueSourceName = ProcessTraceBackendActivityStatsAction.NAME)
private String actionName;
@QField(displayFormat = DisplayFormat.COMMAS)
private Integer callCount;
@QField(displayFormat = DisplayFormat.COMMAS)
private Integer recordCount;
@QField(displayFormat = DisplayFormat.COMMAS)
private Integer runtimeMillis;
/*******************************************************************************
** Default constructor
*******************************************************************************/
public ProcessTraceBackendActivityStats()
{
}
/*******************************************************************************
** Constructor that takes a QRecord
*******************************************************************************/
public ProcessTraceBackendActivityStats(QRecord record)
{
populateFromQRecord(record);
}
/*******************************************************************************
* Getter for id
* @see #withId(Long)
*******************************************************************************/
public Long getId()
{
return (this.id);
}
/*******************************************************************************
* Setter for id
* @see #withId(Long)
*******************************************************************************/
public void setId(Long id)
{
this.id = id;
}
/*******************************************************************************
* Fluent setter for id
*******************************************************************************/
public ProcessTraceBackendActivityStats withId(Long id)
{
this.id = id;
return (this);
}
/*******************************************************************************
* Getter for processTraceId
* @see #withProcessTraceId(Long)
*******************************************************************************/
public Long getProcessTraceId()
{
return (this.processTraceId);
}
/*******************************************************************************
* Setter for processTraceId
* @see #withProcessTraceId(Long)
*******************************************************************************/
public void setProcessTraceId(Long processTraceId)
{
this.processTraceId = processTraceId;
}
/*******************************************************************************
* Fluent setter for processTraceId
*******************************************************************************/
public ProcessTraceBackendActivityStats withProcessTraceId(Long processTraceId)
{
this.processTraceId = processTraceId;
return (this);
}
/*******************************************************************************
* Getter for actionName
* @see #withActionName(String)
*******************************************************************************/
public String getActionName()
{
return (this.actionName);
}
/*******************************************************************************
* Setter for actionName
* @see #withActionName(String)
*******************************************************************************/
public void setActionName(String actionName)
{
this.actionName = actionName;
}
/*******************************************************************************
* Fluent setter for actionName
*******************************************************************************/
public ProcessTraceBackendActivityStats withActionName(String actionName)
{
this.actionName = actionName;
return (this);
}
/*******************************************************************************
* Getter for callCount
* @see #withCallCount(Integer)
*******************************************************************************/
public Integer getCallCount()
{
return (this.callCount);
}
/*******************************************************************************
* Setter for callCount
* @see #withCallCount(Integer)
*******************************************************************************/
public void setCallCount(Integer callCount)
{
this.callCount = callCount;
}
/*******************************************************************************
* Fluent setter for callCount
*******************************************************************************/
public ProcessTraceBackendActivityStats withCallCount(Integer callCount)
{
this.callCount = callCount;
return (this);
}
/*******************************************************************************
* Getter for recordCount
* @see #withRecordCount(Integer)
*******************************************************************************/
public Integer getRecordCount()
{
return (this.recordCount);
}
/*******************************************************************************
* Setter for recordCount
* @see #withRecordCount(Integer)
*******************************************************************************/
public void setRecordCount(Integer recordCount)
{
this.recordCount = recordCount;
}
/*******************************************************************************
* Fluent setter for recordCount
*******************************************************************************/
public ProcessTraceBackendActivityStats withRecordCount(Integer recordCount)
{
this.recordCount = recordCount;
return (this);
}
/*******************************************************************************
* Getter for runtimeMillis
* @see #withRuntimeMillis(Integer)
*******************************************************************************/
public Integer getRuntimeMillis()
{
return (this.runtimeMillis);
}
/*******************************************************************************
* Setter for runtimeMillis
* @see #withRuntimeMillis(Integer)
*******************************************************************************/
public void setRuntimeMillis(Integer runtimeMillis)
{
this.runtimeMillis = runtimeMillis;
}
/*******************************************************************************
* Fluent setter for runtimeMillis
*******************************************************************************/
public ProcessTraceBackendActivityStats withRuntimeMillis(Integer runtimeMillis)
{
this.runtimeMillis = runtimeMillis;
return (this);
}
/*******************************************************************************
* Getter for qqqBackendId
* @see #withQqqBackendId(Integer)
*******************************************************************************/
public Integer getQqqBackendId()
{
return (this.qqqBackendId);
}
/*******************************************************************************
* Setter for qqqBackendId
* @see #withQqqBackendId(Integer)
*******************************************************************************/
public void setQqqBackendId(Integer qqqBackendId)
{
this.qqqBackendId = qqqBackendId;
}
/*******************************************************************************
* Fluent setter for qqqBackendId
*******************************************************************************/
public ProcessTraceBackendActivityStats withQqqBackendId(Integer qqqBackendId)
{
this.qqqBackendId = qqqBackendId;
return (this);
}
/*******************************************************************************
* Getter for qqqTableId
* @see #withQqqTableId(Integer)
*******************************************************************************/
public Integer getQqqTableId()
{
return (this.qqqTableId);
}
/*******************************************************************************
* Setter for qqqTableId
* @see #withQqqTableId(Integer)
*******************************************************************************/
public void setQqqTableId(Integer qqqTableId)
{
this.qqqTableId = qqqTableId;
}
/*******************************************************************************
* Fluent setter for qqqTableId
*******************************************************************************/
public ProcessTraceBackendActivityStats withQqqTableId(Integer qqqTableId)
{
this.qqqTableId = qqqTableId;
return (this);
}
}
@@ -1,131 +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.standardprocesstrace.model;
import java.util.Objects;
import com.kingsrook.qqq.backend.core.actions.tables.CountAction;
import com.kingsrook.qqq.backend.core.actions.tables.DeleteAction;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
import com.kingsrook.qqq.backend.core.actions.tables.UpdateAction;
import com.kingsrook.qqq.backend.core.model.actions.audits.AuditDetailAccumulator;
import com.kingsrook.qqq.backend.core.model.metadata.possiblevalues.PossibleValueEnum;
/*******************************************************************************
** ProcessTraceBackendActivityStatsAction - possible value enum
*******************************************************************************/
public enum ProcessTraceBackendActivityStatsAction implements PossibleValueEnum<String>
{
INSERT(InsertAction.class.getSimpleName(), "Insert"),
UPDATE(UpdateAction.class.getSimpleName(), "Update"),
DELETE(DeleteAction.class.getSimpleName(), "Delete"),
QUERY(QueryAction.class.getSimpleName(), "Query"),
GET(GetAction.class.getSimpleName(), "Get"),
COUNT(CountAction.class.getSimpleName(), "Count"),
AGGREGATE(AuditDetailAccumulator.class.getSimpleName(), "Aggregate");
private final String id;
private final String label;
public static final String NAME = "ProcessTraceBackendActivityStatsAction";
/*******************************************************************************
**
*******************************************************************************/
ProcessTraceBackendActivityStatsAction(String id, String label)
{
this.id = id;
this.label = label;
}
/*******************************************************************************
** Get instance by id
**
*******************************************************************************/
public static ProcessTraceBackendActivityStatsAction getById(String id)
{
if(id == null)
{
return (null);
}
for(ProcessTraceBackendActivityStatsAction value : ProcessTraceBackendActivityStatsAction.values())
{
if(Objects.equals(value.id, id))
{
return (value);
}
}
return (null);
}
/*******************************************************************************
** Getter for id
**
*******************************************************************************/
public String getId()
{
return id;
}
/*******************************************************************************
** Getter for label
**
*******************************************************************************/
public String getLabel()
{
return label;
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getPossibleValueId()
{
return (getId());
}
/*******************************************************************************
**
*******************************************************************************/
@Override
public String getPossibleValueLabel()
{
return (getLabel());
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU Affero General Public License as
* You may obtain a copy of the License at * 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 * You should have received a copy of the GNU Affero General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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.standardprocesstrace.model; package com.kingsrook.qbits.standardprocesstrace.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU Affero General Public License as
* You may obtain a copy of the License at * 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 * You should have received a copy of the GNU Affero General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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.standardprocesstrace.model; package com.kingsrook.qbits.standardprocesstrace.model;
@@ -5,17 +5,18 @@
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU Affero General Public License as
* You may obtain a copy of the License at * 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 * You should have received a copy of the GNU Affero General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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.
*/ */
@@ -5,17 +5,18 @@
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU Affero General Public License as
* You may obtain a copy of the License at * 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 * You should have received a copy of the GNU Affero General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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.
*/ */
@@ -1,297 +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.standardprocesstrace.utils;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceBackendActivityStats;
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.backends.QQQBackendTableManager;
import com.kingsrook.qqq.backend.core.model.tables.QQQTableTableManager;
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
import com.kingsrook.qqq.backend.core.utils.PrefixedDefaultThreadFactory;
import com.kingsrook.qqq.backend.core.utils.StringUtils;
import static com.kingsrook.qqq.backend.core.logging.LogUtils.logPair;
/*******************************************************************************
* Singleton that collects QueryStats, associated with a processTraceId.
*
* <p>Runs a scheduled executor thread to clean itself up - e.g., in case processes
* leak (e.g., started in a frontend but exited before final step). Configured by
* {@link #setCleanupIntervalMillis(long)} and {@link #setCleanupMaxAge(long)}</p>
*******************************************************************************/
public class ProcessTraceBackendActivityStatsManager
{
private static final QLogger LOG = QLogger.getLogger(ProcessTraceBackendActivityStatsManager.class);
private static ProcessTraceBackendActivityStatsManager processTraceBackendActivityStatsManager = null;
private Map<Long, ProcessStats> stats = Collections.synchronizedMap(new HashMap<>());
private long CLEANUP_INTERVAL_MILLIS = 60 * 60 * 1000;
private long CLEANUP_MAX_AGE = 12 * 60 * 60 * 1000;
private ScheduledExecutorService scheduledExecutorService;
/***************************************************************************
* container for all data collected for a particular process trace.
*
* stores start milliseconds (for cleanup), plus map of keys to stats.
***************************************************************************/
private record ProcessStats(long startMillis, Map<Key, Stats> stats)
{
/*******************************************************************************
** Constructor that uses default values
**
*******************************************************************************/
public ProcessStats()
{
this(System.currentTimeMillis(), new LinkedHashMap<>());
}
}
/***************************************************************************
* what backend/table/action a set of stats are associated with.
*
* e.g., memoryBackend, personTable, insertAction
***************************************************************************/
public record Key(String backendName, String tableName, String actionName) implements Serializable
{
}
/***************************************************************************
* the stats collected for individual keys.
***************************************************************************/
private record Stats(Integer calls, Integer records, Integer millis)
{
}
/*******************************************************************************
** Singleton constructor
*******************************************************************************/
private ProcessTraceBackendActivityStatsManager()
{
scheduleCleanupJob();
}
/***************************************************************************
*
***************************************************************************/
private void scheduleCleanupJob()
{
scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new PrefixedDefaultThreadFactory("ProcessTraceBackendActivityStatsManager-cleanup"));
scheduledExecutorService.scheduleAtFixedRate(this::cleanup, CLEANUP_INTERVAL_MILLIS, CLEANUP_INTERVAL_MILLIS, TimeUnit.MILLISECONDS);
}
/*******************************************************************************
** Singleton accessor
*******************************************************************************/
public static ProcessTraceBackendActivityStatsManager getInstance()
{
if(processTraceBackendActivityStatsManager == null)
{
processTraceBackendActivityStatsManager = new ProcessTraceBackendActivityStatsManager();
}
return (processTraceBackendActivityStatsManager);
}
/***************************************************************************
* shutdown the scheduled job. also clear stats.
***************************************************************************/
public void shutdown()
{
scheduledExecutorService.shutdown();
stats.clear();
}
/***************************************************************************
* initialize storage for stats for a specific process trace id
***************************************************************************/
public void initProcess(Long processTraceId)
{
stats.put(processTraceId, new ProcessStats());
}
/***************************************************************************
* clean up stat entries for processes that were originally created before
* CLEANUP_MAX_AGE (per {@link #setCleanupMaxAge(long)} milliseconds ago.
***************************************************************************/
private void cleanup()
{
long limitStartMillis = System.currentTimeMillis() - CLEANUP_MAX_AGE;
LOG.info("Starting cleanup of ProcessTraceBackendActivityStatsManager", logPair("size", stats.size()));
stats.entrySet().removeIf(entry -> entry.getValue().startMillis < limitStartMillis);
LOG.info("Finished cleanup of ProcessTraceBackendActivityStatsManager", logPair("size", stats.size()));
}
/***************************************************************************
* when storing a processTrace (e.g., after the process has finished running),
* convert the stats stored in this object to a list of {@link ProcessTraceBackendActivityStats}
* entities. Also remove the stats from this object's internal storage.
***************************************************************************/
public List<ProcessTraceBackendActivityStats> getAndRemoveStats(Long processTraceId) throws QException
{
List<ProcessTraceBackendActivityStats> rs = new ArrayList<>();
ProcessStats processStats = stats.remove(processTraceId);
if(processStats != null)
{
for(Map.Entry<Key, Stats> entry : CollectionUtils.nonNullMap(processStats.stats()).entrySet())
{
rs.add(new ProcessTraceBackendActivityStats()
.withProcessTraceId(processTraceId)
.withQqqBackendId(getBackendId(entry.getKey().backendName()))
.withQqqTableId(getTableId(entry.getKey().tableName()))
.withActionName(entry.getKey().actionName())
.withCallCount(entry.getValue().calls())
.withRecordCount(entry.getValue().records())
.withRuntimeMillis(entry.getValue().millis())
);
}
}
return (rs);
}
/***************************************************************************
*
***************************************************************************/
private Integer getBackendId(String backendName)
{
if(!StringUtils.hasContent(backendName))
{
return (null);
}
try
{
return QQQBackendTableManager.getQQQBackendId(QContext.getQInstance(), backendName);
}
catch(Exception e)
{
return (null);
}
}
/***************************************************************************
*
***************************************************************************/
private Integer getTableId(String tableName)
{
if(!StringUtils.hasContent(tableName))
{
return (null);
}
try
{
return QQQTableTableManager.getQQQTableId(QContext.getQInstance(), tableName);
}
catch(Exception e)
{
return (null);
}
}
/***************************************************************************
* increment the stat values for a given process.
*
* @param key the backend, table, and action
* @param addCalls the number of calls to add (typically 1 at a time?)
* @param addRecords the number of records to add
* @param addMillis the number of milliseconds of runtime to add
***************************************************************************/
public void add(Long processTraceId, Key key, Integer addCalls, Integer addRecords, Integer addMillis)
{
ProcessStats processStats = stats.computeIfAbsent(processTraceId, k -> new ProcessStats());
Map<Key, Stats> countsForProcess = processStats.stats();
if(countsForProcess != null)
{
Stats statsForKey = countsForProcess.computeIfAbsent(key, (k) -> new Stats(0, 0, 0));
countsForProcess.put(key, new Stats(statsForKey.calls + addCalls, statsForKey.records + addRecords, statsForKey.millis + addMillis));
}
}
/***************************************************************************
* sets the cleanup job's scheduled interval. Also reschedules the job.
*
* @param cleanupIntervalMillis milliseconds between runs of the cleanup job
***************************************************************************/
public void setCleanupIntervalMillis(long cleanupIntervalMillis)
{
CLEANUP_INTERVAL_MILLIS = cleanupIntervalMillis;
scheduledExecutorService.shutdown();
scheduleCleanupJob();
}
/***************************************************************************
* @param cleanupMaxAge max age (in millis) that a process is allowed to be
* before it gets cleaned up when a cleanup job runs.
***************************************************************************/
public void setCleanupMaxAge(long cleanupMaxAge)
{
CLEANUP_MAX_AGE = cleanupMaxAge;
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU Affero General Public License as
* You may obtain a copy of the License at * 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 * You should have received a copy of the GNU Affero General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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.standardprocesstrace.utils; package com.kingsrook.qbits.standardprocesstrace.utils;
@@ -5,17 +5,18 @@
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU Affero General Public License as
* You may obtain a copy of the License at * 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 * You should have received a copy of the GNU Affero General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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.standardprocesstrace.utils; package com.kingsrook.qbits.standardprocesstrace.utils;
@@ -5,17 +5,18 @@
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU Affero General Public License as
* You may obtain a copy of the License at * 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 * You should have received a copy of the GNU Affero General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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.standardprocesstrace.utils; package com.kingsrook.qbits.standardprocesstrace.utils;
@@ -1,66 +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.standardprocesstrace.utils;
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatConsumerInterface;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.model.querystats.QueryStat;
/*******************************************************************************
* Standard Process Trace's implementation of a QueryStat consumer.
*
* <p>That is, to accumulate them in the {@link ProcessTraceBackendActivityStatsManager},
* associated with the processTraceId in the user's session, so they can be stored
* when the process trace is stored.</p>
*******************************************************************************/
public class StandardProcessTraceQueryStatConsumer implements QueryStatConsumerInterface
{
private static final QLogger LOG = QLogger.getLogger(StandardProcessTraceQueryStatConsumer.class);
/***************************************************************************
*
***************************************************************************/
@Override
public void accept(QueryStat queryStat)
{
try
{
String processTraceId = QContext.getQSession().getValue(StandardProcessTracer.PROCESS_TRACE_ID_SESSION_KEY);
if(processTraceId == null)
{
return;
}
String backendName = QContext.getQInstance().getBackendForTable(queryStat.getTableName()).getName();
String backendAction = queryStat.getBackendAction();
ProcessTraceBackendActivityStatsManager.Key key = new ProcessTraceBackendActivityStatsManager.Key(backendName, queryStat.getTableName(), backendAction);
ProcessTraceBackendActivityStatsManager.getInstance().add(Long.valueOf(processTraceId), key, 1, queryStat.getRecordCount(), queryStat.getFirstResultMillis());
}
catch(Exception e)
{
LOG.warn("Error accepting query stats for process trace", e);
}
}
}
@@ -5,17 +5,18 @@
* contact@kingsrook.com * contact@kingsrook.com
* https://github.com/Kingsrook/ * https://github.com/Kingsrook/
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * This program is free software: you can redistribute it and/or modify
* you may not use this file except in compliance with the License. * it under the terms of the GNU Affero General Public License as
* You may obtain a copy of the License at * 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 * You should have received a copy of the GNU Affero General Public License
* distributed under the License is distributed on an "AS IS" BASIS, * along with this program. If not, see <https://www.gnu.org/licenses/>.
* 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.
*/ */
@@ -29,7 +30,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import com.kingsrook.qbits.standardprocesstrace.StandardProcessTraceQBitConfig; import com.kingsrook.qbits.standardprocesstrace.StandardProcessTraceQBitConfig;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTrace; import com.kingsrook.qbits.standardprocesstrace.model.ProcessTrace;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceBackendActivityStats;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceSummaryLine; import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceSummaryLine;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceSummaryLineRecordInt; import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceSummaryLineRecordInt;
import com.kingsrook.qqq.backend.core.actions.tables.GetAction; import com.kingsrook.qqq.backend.core.actions.tables.GetAction;
@@ -75,8 +75,6 @@ public class StandardProcessTracer implements ProcessTracerInterface
private static StandardProcessTraceQBitConfig standardProcessTraceQBitConfig; private static StandardProcessTraceQBitConfig standardProcessTraceQBitConfig;
public static final String PROCESS_TRACE_ID_SESSION_KEY = StandardProcessTracer.class.getName() + ".processTraceId";
private Instant startTime; private Instant startTime;
private Long processTraceId; private Long processTraceId;
@@ -100,17 +98,6 @@ public class StandardProcessTracer implements ProcessTracerInterface
.withProcessUUID(runProcessInput.getProcessUUID()))); .withProcessUUID(runProcessInput.getProcessUUID())));
processTraceId = insertOutput.getRecords().get(0).getValueLong("id"); processTraceId = insertOutput.getRecords().get(0).getValueLong("id");
////////////////////////////////////////////////////////////////////////////////////
// store the processTraceId in the session. this will be used by the //
// ProcessTraceQueryStatConsumer to associated QueryStats with this process trace //
////////////////////////////////////////////////////////////////////////////////////
if(QContext.getQSession() != null)
{
QContext.getQSession().setValue(PROCESS_TRACE_ID_SESSION_KEY, String.valueOf(processTraceId));
}
ProcessTraceBackendActivityStatsManager.getInstance().initProcess(processTraceId);
} }
catch(QException e) catch(QException e)
{ {
@@ -126,29 +113,9 @@ public class StandardProcessTracer implements ProcessTracerInterface
@Override @Override
public void handleProcessResume(RunProcessInput runProcessInput) public void handleProcessResume(RunProcessInput runProcessInput)
{ {
try /////////////////////////////////
{ // noop in this implementation //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// /////////////////////////////////
// if a user is resuming a process, e.g., from a frontend, then we want to put that processTraceId in their session //
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(processTraceId == null)
{
QRecord processTrace = GetAction.execute(ProcessTrace.TABLE_NAME, Map.of("processUUID", runProcessInput.getProcessUUID()));
if(processTrace != null)
{
processTraceId = processTrace.getValueLong("id");
}
}
if(processTraceId != null && QContext.getQSession() != null)
{
QContext.getQSession().setValue(PROCESS_TRACE_ID_SESSION_KEY, String.valueOf(processTraceId));
}
}
catch(Exception e)
{
LOG.warn("Error resuming process trace", e, logPair("processUUID", () -> runProcessInput.getProcessUUID()));
}
} }
@@ -361,28 +328,12 @@ public class StandardProcessTracer implements ProcessTracerInterface
new InsertAction().execute(new InsertInput(ProcessTraceSummaryLine.TABLE_NAME).withRecordEntities(summaryLines)); new InsertAction().execute(new InsertInput(ProcessTraceSummaryLine.TABLE_NAME).withRecordEntities(summaryLines));
} }
//////////////////////////////////////////
// build and store backend stat records //
//////////////////////////////////////////
if(QContext.getQInstance().getTable(ProcessTraceBackendActivityStats.TABLE_NAME) != null)
{
List<ProcessTraceBackendActivityStats> stats = ProcessTraceBackendActivityStatsManager.getInstance().getAndRemoveStats(processTraceId);
new InsertAction().execute(new InsertInput(ProcessTraceBackendActivityStats.TABLE_NAME).withRecordEntities(stats));
}
} }
} }
catch(Exception e) catch(Exception e)
{ {
LOG.warn("Error completing store of processTrace records.", e); LOG.warn("Error completing store of processTrace records.", e);
} }
finally
{
if(QContext.getQSession() != null)
{
QContext.getQSession().removeValue(PROCESS_TRACE_ID_SESSION_KEY);
}
}
} }
@@ -1,85 +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.standardprocesstrace;
import java.util.List;
import com.kingsrook.qbits.standardprocesstrace.utils.StandardProcessTraceQueryStatConsumer;
import com.kingsrook.qqq.backend.core.actions.tables.helpers.QueryStatManager;
import com.kingsrook.qqq.backend.core.context.QContext;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.QInstanceValidator;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.session.QSystemUserSession;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryRecordStore;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
/*******************************************************************************
* base class for tests for this QBit.
*******************************************************************************/
public class BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@BeforeEach
void baseBeforeEach() throws Exception
{
QInstance qInstance = defineQInstance();
new QInstanceValidator().validate(qInstance);
QContext.init(qInstance, new QSystemUserSession());
QueryStatManager queryStatManager = QueryStatManager.getInstance();
queryStatManager.start(qInstance, () -> new QSystemUserSession());
queryStatManager.setQueryStatConsumers(List.of(new StandardProcessTraceQueryStatConsumer()));
MemoryRecordStore.fullReset();
}
/*******************************************************************************
**
*******************************************************************************/
@AfterEach
void afterEach()
{
MemoryRecordStore.fullReset();
QueryStatManager.getInstance().stop();
}
/***************************************************************************
**
***************************************************************************/
private QInstance defineQInstance() throws QException
{
return new StandardProcessTraceTestApplication().defineQInstance();
}
}
@@ -1,233 +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.standardprocesstrace;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import com.kingsrook.qbits.standardprocesstrace.utils.StandardProcessTracer;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.instances.AbstractQQQApplication;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLine;
import com.kingsrook.qqq.backend.core.model.actions.processes.ProcessSummaryLineInterface;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunBackendStepOutput;
import com.kingsrook.qqq.backend.core.model.actions.processes.Status;
import com.kingsrook.qqq.backend.core.model.backends.QQQBackendsMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.metadata.MetaDataProducerMultiOutput;
import com.kingsrook.qqq.backend.core.model.metadata.QAuthenticationType;
import com.kingsrook.qqq.backend.core.model.metadata.QBackendMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.QInstance;
import com.kingsrook.qqq.backend.core.model.metadata.audits.AuditLevel;
import com.kingsrook.qqq.backend.core.model.metadata.audits.QAuditRules;
import com.kingsrook.qqq.backend.core.model.metadata.authentication.QAuthenticationMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.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.layout.QAppMetaData;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QAppSection;
import com.kingsrook.qqq.backend.core.model.metadata.layout.QIcon;
import com.kingsrook.qqq.backend.core.model.metadata.tables.Capability;
import com.kingsrook.qqq.backend.core.model.metadata.tables.QTableMetaData;
import com.kingsrook.qqq.backend.core.model.processes.QQQProcessesMetaDataProvider;
import com.kingsrook.qqq.backend.core.model.tables.QQQTablesMetaDataProvider;
import com.kingsrook.qqq.backend.core.modules.backend.implementations.memory.MemoryBackendModule;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.AbstractTransformStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.ExtractViaQueryStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.LoadViaInsertStep;
import com.kingsrook.qqq.backend.core.processes.implementations.etl.streamedwithfrontend.StreamedETLWithFrontendProcess;
/*******************************************************************************
* test application (e.g., QInstance) for this QBit's tests.
*******************************************************************************/
public class StandardProcessTraceTestApplication extends AbstractQQQApplication
{
public static final String MEMORY_BACKEND_NAME = "memory";
public static final String TABLE_NAME_PERSON = "person";
public static final String TABLE_NAME_ORDER = "order";
public static final String PROCESS_NAME_PLACE_ORDERS = "placeOrders";
/***************************************************************************
**
***************************************************************************/
@Override
public QInstance defineQInstance() throws QException
{
QInstance qInstance = new QInstance();
qInstance.withInstanceDefaultAuthentication(new QAuthenticationMetaData().withType(QAuthenticationType.FULLY_ANONYMOUS));
qInstance.addBackend(new QBackendMetaData()
.withName(MEMORY_BACKEND_NAME)
.withBackendType(MemoryBackendModule.class));
//////////////////////////////
// add tables table and PVS //
//////////////////////////////
new QQQTablesMetaDataProvider().defineAll(qInstance, MEMORY_BACKEND_NAME, MEMORY_BACKEND_NAME, null);
new QQQProcessesMetaDataProvider().defineAll(qInstance, MEMORY_BACKEND_NAME, MEMORY_BACKEND_NAME, null);
new QQQBackendsMetaDataProvider().defineAll(qInstance, MEMORY_BACKEND_NAME, MEMORY_BACKEND_NAME, null);
//////////////////////
// produce our qbit //
//////////////////////
StandardProcessTraceQBitProducer producer = new StandardProcessTraceQBitProducer()
.withStandardProcessTraceQBitConfig(new StandardProcessTraceQBitConfig()
.withTableMetaDataCustomizer((QInstance instance, QTableMetaData table) ->
{
table.setBackendName(MEMORY_BACKEND_NAME);
return (table);
})
.withIncludeBackendActivityStats(true));
MetaDataProducerMultiOutput allQBitMetaData = producer.produce(qInstance);
allQBitMetaData.addSelfToInstance(qInstance);
////////////////////////////////////////
// produce some test tables //
// note - nice to be a qbit this too? //
////////////////////////////////////////
qInstance.addTable(new QTableMetaData()
.withName(TABLE_NAME_PERSON)
.withBackendName(MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("firstName", QFieldType.STRING).withIsEditable(true))
.withField(new QFieldMetaData("lastName", QFieldType.STRING).withIsEditable(true))
.withCapability(Capability.QUERY_STATS)
);
qInstance.addTable(new QTableMetaData()
.withName(TABLE_NAME_ORDER)
.withBackendName(MEMORY_BACKEND_NAME)
.withPrimaryKeyField("id")
.withField(new QFieldMetaData("id", QFieldType.INTEGER).withIsEditable(false))
.withField(new QFieldMetaData("orderNo", QFieldType.STRING).withIsEditable(true))
.withField(new QFieldMetaData("storeId", QFieldType.INTEGER).withIsEditable(true))
.withField(new QFieldMetaData("shipTo", QFieldType.STRING).withIsEditable(true))
.withCapability(Capability.QUERY_STATS)
);
qInstance.addProcess(StreamedETLWithFrontendProcess.processMetaDataBuilder()
.withName(PROCESS_NAME_PLACE_ORDERS)
.withTableName(TABLE_NAME_PERSON)
.withSourceTable(TABLE_NAME_PERSON)
.withDestinationTable(TABLE_NAME_ORDER)
.withExtractStepClass(ExtractViaQueryStep.class)
.withTransformStepClass(PlaceOrdersProcessTransformStep.class)
.withLoadStepClass(LoadViaInsertStep.class)
.getProcessMetaData()
.withProcessTracerCodeReference(new QCodeReference(StandardProcessTracer.class))
);
///////////////////////////////////////////
// turn off audits (why on by default??) //
///////////////////////////////////////////
qInstance.getTables().values().forEach(t -> t.setAuditRules(new QAuditRules().withAuditLevel(AuditLevel.NONE)));
/////////////////
// create apps //
/////////////////
qInstance.addApp(new QAppMetaData()
.withName("logs")
.withIcon(new QIcon("history_edu"))
.withSection(StandardProcessTraceQBitProducer.getAppSection(qInstance)));
qInstance.addApp(new QAppMetaData()
.withName("testData")
.withIcon(new QIcon("dataset"))
.withSection(new QAppSection()
.withName("people")
.withTables(List.of(TABLE_NAME_PERSON))));
return qInstance;
}
/***************************************************************************
* test process transformer - for each input person, creates an output order (to insert)
***************************************************************************/
public static class PlaceOrdersProcessTransformStep extends AbstractTransformStep
{
private ProcessSummaryLine okLine = new ProcessSummaryLine(Status.OK)
.withMessageSuffix("")
.withSingularFutureMessage("will get a new order")
.withPluralFutureMessage("will get new orders")
.withSingularPastMessage("got a new order")
.withPluralPastMessage("got new orders");
private ProcessSummaryLine errorLine = new ProcessSummaryLine(Status.ERROR)
.withMessageSuffix("")
.withSingularFutureMessage("will not get a new order")
.withPluralFutureMessage("will not get new orders")
.withSingularPastMessage("did not get a new order")
.withPluralPastMessage("did not get new orders");
/***************************************************************************
*
***************************************************************************/
@Override
public ArrayList<ProcessSummaryLineInterface> getProcessSummary(RunBackendStepOutput runBackendStepOutput, boolean isForResultScreen)
{
ArrayList<ProcessSummaryLineInterface> rs = new ArrayList<>();
okLine.addSelfToListIfAnyCount(rs);
errorLine.addSelfToListIfAnyCount(rs);
return rs;
}
/***************************************************************************
*
***************************************************************************/
@Override
public void runOnePage(RunBackendStepInput runBackendStepInput, RunBackendStepOutput runBackendStepOutput) throws QException
{
for(QRecord personRecord : runBackendStepInput.getRecords())
{
////////////////////////////////////////
// don't let Mr. Burns order anything //
////////////////////////////////////////
if("Burns".equals(personRecord.getValueString("lastName")))
{
errorLine.incrementCountAndAddPrimaryKey(personRecord.getValue("id"));
continue;
}
QRecord orderRecord = new QRecord()
.withValue("orderNo", UUID.randomUUID().toString())
.withValue("shipTo", personRecord.getValueString("firstName") + " " + personRecord.getValueString("lastName"));
runBackendStepOutput.addRecord(orderRecord);
okLine.incrementCountAndAddPrimaryKey(personRecord.getValue("id"));
}
}
}
}
@@ -1,125 +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.standardprocesstrace.utils;
import java.util.List;
import java.util.concurrent.TimeUnit;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceBackendActivityStats;
import com.kingsrook.qqq.backend.core.exceptions.QException;
import com.kingsrook.qqq.backend.core.logging.QCollectingLogger;
import com.kingsrook.qqq.backend.core.logging.QLogger;
import com.kingsrook.qqq.backend.core.utils.SleepUtils;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
/*******************************************************************************
** Unit test for ProcessTraceBackendActivityStatsManager
*******************************************************************************/
class ProcessTraceBackendActivityStatsManagerTest
{
ProcessTraceBackendActivityStatsManager.Key key = new ProcessTraceBackendActivityStatsManager.Key("b", "t", "a");
/*******************************************************************************
**
*******************************************************************************/
@AfterEach
void afterEach()
{
ProcessTraceBackendActivityStatsManager manager = ProcessTraceBackendActivityStatsManager.getInstance();
manager.shutdown();
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testMaxAgeLarger() throws QException
{
long processTraceId = 1L;
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(ProcessTraceBackendActivityStatsManager.class);
ProcessTraceBackendActivityStatsManager manager = ProcessTraceBackendActivityStatsManager.getInstance();
manager.initProcess(processTraceId);
manager.add(processTraceId, key, 1, 2, 3);
manager.setCleanupIntervalMillis(10);
manager.setCleanupMaxAge(100);
SleepUtils.sleep(50, TimeUnit.MILLISECONDS);
QLogger.deactivateCollectingLoggerForClass(ProcessTraceBackendActivityStatsManager.class);
assertThat(collectingLogger.getCollectedMessages()).allMatch(clm -> clm.getMessage().contains("\"size\":1"));
List<ProcessTraceBackendActivityStats> stats = manager.getAndRemoveStats(processTraceId);
assertEquals(1, stats.size());
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testMaxAgeSmaller() throws QException
{
long processTraceId = 2L;
QCollectingLogger collectingLogger = QLogger.activateCollectingLoggerForClass(ProcessTraceBackendActivityStatsManager.class);
ProcessTraceBackendActivityStatsManager manager = ProcessTraceBackendActivityStatsManager.getInstance();
manager.initProcess(processTraceId);
manager.add(processTraceId, key, 1, 2, 3);
manager.setCleanupIntervalMillis(5);
manager.setCleanupMaxAge(20);
// Wait for the scheduled cleanup rather than assuming a shared CI runner
// will schedule the background thread within 50 milliseconds.
long deadline = System.nanoTime() + TimeUnit.SECONDS.toNanos(2);
while(System.nanoTime() < deadline)
{
var messages = collectingLogger.getCollectedMessages();
if(!messages.isEmpty() && messages.get(messages.size() - 1).getMessage().contains("\"size\":0"))
{
break;
}
SleepUtils.sleep(5, TimeUnit.MILLISECONDS);
}
QLogger.deactivateCollectingLoggerForClass(ProcessTraceBackendActivityStatsManager.class);
assertThat(collectingLogger.getCollectedMessages()).anyMatch(clm -> clm.getMessage().contains("\"size\":1"));
assertThat(collectingLogger.getCollectedMessages()).anyMatch(clm -> clm.getMessage().contains("\"size\":0"));
List<ProcessTraceBackendActivityStats> stats = manager.getAndRemoveStats(processTraceId);
assertEquals(0, stats.size());
////////////////////////////////////////////////////////////////////////////////////////////
// make sure even after a cleanup, we don't fail if we do try to add values for a process //
////////////////////////////////////////////////////////////////////////////////////////////
manager.add(processTraceId, key, 1, 2, 3);
stats = manager.getAndRemoveStats(processTraceId);
assertEquals(1, stats.size());
}
}
@@ -1,176 +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.standardprocesstrace.utils;
import java.util.List;
import com.kingsrook.qbits.standardprocesstrace.BaseTest;
import com.kingsrook.qbits.standardprocesstrace.StandardProcessTraceTestApplication;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTrace;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceBackendActivityStats;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceSummaryLine;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceSummaryLineRecordInt;
import com.kingsrook.qbits.standardprocesstrace.model.ProcessTraceSummaryStatusEnum;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallbackFactory;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
import com.kingsrook.qqq.backend.core.actions.tables.InsertAction;
import com.kingsrook.qqq.backend.core.actions.tables.QueryAction;
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.RunProcessInput;
import com.kingsrook.qqq.backend.core.model.actions.processes.RunProcessOutput;
import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria;
import com.kingsrook.qqq.backend.core.model.actions.tables.query.QQueryFilter;
import com.kingsrook.qqq.backend.core.model.data.QRecord;
import com.kingsrook.qqq.backend.core.model.tables.QQQTableTableManager;
import org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
/*******************************************************************************
** Unit test for StandardProcessTracer
*******************************************************************************/
class StandardProcessTracerTest extends BaseTest
{
/*******************************************************************************
**
*******************************************************************************/
@Test
void test() throws QException
{
insertHomerMargeAndMrBurns();
////////////////////////////////////////
// run process skipping backend steps //
////////////////////////////////////////
RunProcessInput input = new RunProcessInput();
input.setProcessName(StandardProcessTraceTestApplication.PROCESS_NAME_PLACE_ORDERS);
input.setCallback(QProcessCallbackFactory.forFilter(new QQueryFilter()));
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.SKIP);
new RunProcessAction().execute(input);
baseAssertionsAfterProcess();
Integer personTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), StandardProcessTraceTestApplication.TABLE_NAME_PERSON);
Integer orderTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), StandardProcessTraceTestApplication.TABLE_NAME_ORDER);
List<QRecord> processTraceBackendActivityStatRecords = QueryAction.execute(ProcessTraceBackendActivityStats.TABLE_NAME, new QQueryFilter());
assertThat(processTraceBackendActivityStatRecords)
.hasSizeGreaterThanOrEqualTo(2)
.anyMatch(r -> r.getValue("qqqTableId").equals(personTableId) && r.getValue("actionName").equals(QueryAction.class.getSimpleName()) && r.getValueInteger("callCount").equals(1) && r.getValueInteger("recordCount").equals(3))
.anyMatch(r -> r.getValue("qqqTableId").equals(orderTableId) && r.getValue("actionName").equals(InsertAction.class.getSimpleName()) && r.getValueInteger("callCount").equals(1) && r.getValueInteger("recordCount").equals(2));
}
/*******************************************************************************
**
*******************************************************************************/
@Test
void testResumingFrontendProcess() throws QException
{
insertHomerMargeAndMrBurns();
//////////////////////////////////////
// start process - break at preview //
//////////////////////////////////////
RunProcessInput input = new RunProcessInput();
input.setProcessName(StandardProcessTraceTestApplication.PROCESS_NAME_PLACE_ORDERS);
input.setCallback(QProcessCallbackFactory.forFilter(new QQueryFilter()));
input.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK);
RunProcessOutput output = new RunProcessAction().execute(input);
/////////////////////////////////////
// continue to execute the process //
/////////////////////////////////////
input.setProcessUUID(output.getProcessUUID());
input.setStartAfterStep(output.getProcessState().getNextStepName().get());
new RunProcessAction().execute(input);
baseAssertionsAfterProcess();
Integer personTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), StandardProcessTraceTestApplication.TABLE_NAME_PERSON);
Integer orderTableId = QQQTableTableManager.getQQQTableId(QContext.getQInstance(), StandardProcessTraceTestApplication.TABLE_NAME_ORDER);
List<QRecord> processTraceBackendActivityStatRecords = QueryAction.execute(ProcessTraceBackendActivityStats.TABLE_NAME, new QQueryFilter());
assertThat(processTraceBackendActivityStatRecords)
.hasSizeGreaterThanOrEqualTo(2)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// this time, there'll be 2 calls to the query (for preview, and execute steps), for twice as many records total //
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////
.anyMatch(r -> r.getValue("qqqTableId").equals(personTableId) && r.getValue("actionName").equals(QueryAction.class.getSimpleName()) && r.getValueInteger("callCount").equals(2) && r.getValueInteger("recordCount").equals(6))
.anyMatch(r -> r.getValue("qqqTableId").equals(orderTableId) && r.getValue("actionName").equals(InsertAction.class.getSimpleName()) && r.getValueInteger("callCount").equals(1) && r.getValueInteger("recordCount").equals(2));
}
/***************************************************************************
*
***************************************************************************/
private static void insertHomerMargeAndMrBurns() throws QException
{
///////////////////////////////////////////////////////////////////////////////////////////////////////////
// insert 3 people to run through the process. //
// the process will insert an order for Homer & Marge, but is hard-coded to make an error for Mr. Burns. //
///////////////////////////////////////////////////////////////////////////////////////////////////////////
new InsertAction().execute(new InsertInput(StandardProcessTraceTestApplication.TABLE_NAME_PERSON).withRecords(List.of(
new QRecord().withValue("id", 100).withValue("firstName", "Homer").withValue("lastName", "Simpson"),
new QRecord().withValue("id", 101).withValue("firstName", "Marge").withValue("lastName", "Simpson"),
new QRecord().withValue("id", 102).withValue("firstName", "Mr.").withValue("lastName", "Burns")
)));
}
/***************************************************************************
*
***************************************************************************/
private static void baseAssertionsAfterProcess() throws QException
{
List<QRecord> processTraceRecords = QueryAction.execute(ProcessTrace.TABLE_NAME, new QQueryFilter());
assertEquals(1, processTraceRecords.size());
List<QRecord> processTraceSummaryLineRecords = QueryAction.execute(ProcessTraceSummaryLine.TABLE_NAME, new QQueryFilter());
assertEquals(2, processTraceSummaryLineRecords.size());
QRecord okSummaryLineRecord = processTraceSummaryLineRecords.stream().filter(r -> r.getValueString("status").equals(ProcessTraceSummaryStatusEnum.OK.getPossibleValueId())).findFirst().get();
assertNotNull(okSummaryLineRecord);
assertEquals(2, okSummaryLineRecord.getValueInteger("recordCount"));
List<QRecord> okRecordInts = QueryAction.execute(ProcessTraceSummaryLineRecordInt.TABLE_NAME, new QQueryFilter(new QFilterCriteria("processTraceSummaryLineId", QCriteriaOperator.EQUALS, okSummaryLineRecord.getValueLong("id"))));
assertEquals(2, okRecordInts.size());
assertThat(okRecordInts).allMatch(r -> r.getValueInteger("recordId").equals(100) || r.getValueInteger("recordId").equals(101));
QRecord errorSummaryLineRecord = processTraceSummaryLineRecords.stream().filter(r -> r.getValueString("status").equals(ProcessTraceSummaryStatusEnum.ERROR.getPossibleValueId())).findFirst().get();
assertNotNull(errorSummaryLineRecord);
assertEquals(1, errorSummaryLineRecord.getValueInteger("recordCount"));
List<QRecord> errorRecordInts = QueryAction.execute(ProcessTraceSummaryLineRecordInt.TABLE_NAME, new QQueryFilter(new QFilterCriteria("processTraceSummaryLineId", QCriteriaOperator.EQUALS, errorSummaryLineRecord.getValueLong("id"))));
assertEquals(1, errorRecordInts.size());
assertEquals(102, errorRecordInts.get(0).getValueInteger("recordId"));
}
}