Compare commits

..
16 Commits
Author SHA1 Message Date
forge-recovery 64087ba10e ci: enroll shared Gitea lifecycle
Shared QRun source version / version (push) Failing after 10m18s
qrun-release / postpublish (push) Failing after 10m18s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (push) Successful in 1m14s
Shared Java Maven CI with Docker / verify (push) Successful in 1m32s
QRun-IO/ci-policy: ci / ci-result (push) Successful in 3s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (push) Successful in 13s
QRun-IO/ci-policy: ci / java-docker-3 (push) Successful in 32m16s
Shared Java Maven CI with Docker / gate (push) Successful in 21s
qrun-release / release (push) Successful in 32m59s
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-4 (push) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (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-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 with Docker / secrets (push) Successful in 20s
QRun-IO/ci-policy: native / native-result (push) Successful in 6s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (push) Successful in 18s
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: ci / java-maven-6-head (push) Skipped
QRun-IO/ci-policy: ci / java-maven-7-head (push) Skipped
2026-11-02 22:25:21 +00:00
332ffe8b20 ci: add the isolated postpublish job to the QRun release caller
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-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-frontend-browser-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-6 (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 / 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 with Docker / secrets (push) Successful in 13s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (pull_request) Successful in 12s
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
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 5s
Shared Java Maven CI with Docker / gate (push) Canceled after 0s
Shared Java Maven CI with Docker / verify (push) Canceled after 0s
qrun-release / release (push) Canceled after 2h24m47s
qrun-release / postpublish (push) Canceled after 0s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (pull_request) Successful in 1m36s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (pull_request) Successful in 9s
QRun-IO/ci-policy: ci / java-docker-3 (pull_request) Successful in 3h9m7s
QRun-IO/ci-policy: ci / ci-result (pull_request) Successful in 3s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 15:56:09 +00:00
047f299e34 ci: move the QRun release caller to the thin v2 contract
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (pull_request) Successful in 11s
Shared Java Maven CI with Docker / secrets (push) Successful in 11s
QRun-IO/ci-policy: native / native-result (pull_request) Successful in 3s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (pull_request) Canceled after 0s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (pull_request) Canceled after 0s
QRun-IO/ci-policy: ci / java-docker-2 (pull_request) Canceled after 6m59s
Shared Java Maven CI with Docker / verify (push) Canceled after 0s
Shared Java Maven CI with Docker / gate (push) Canceled after 0s
QRun-IO/ci-policy: ci / fork-guard (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-frontend-browser-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-9 (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
qrun-release / release (push) Canceled after 7m1s
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 6s
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 06:22:29 +00:00
forge-recoveryandCodex 30310b65de ci: add the identical QRun release caller
QRun-IO/ci-policy: ci / fork-guard (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-frontend-browser-3 (pull_request) Skipped
QRun-IO/ci-policy: ci / java-maven-4 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-5 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-6 (pull_request) Skipped
QRun-IO/ci-policy: ci / node-playwright-7 (pull_request) Skipped
QRun-IO/ci-policy: ci / source-config-8 (pull_request) Skipped
QRun-IO/ci-policy: ci / not-enrolled (pull_request) Skipped
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (pull_request) Failing after 1s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (pull_request) Successful in 58s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (pull_request) Failing after 9s
QRun-IO/ci-policy: ci / java-docker-2 (pull_request) Failing after 1h0m29s
QRun-IO/ci-policy: ci / ci-result (pull_request) Failing after 3s
Co-Authored-By: Codex <noreply@openai.com>
2026-11-02 04:17:17 +00:00
james.maesandClaude Fable 5.1 6c56658207 fix(processes): keep SendTestEventToWebhook's records past its input screen (#41)
QRun-IO/ci-policy: ci / fork-guard (push) Skipped
QRun-IO/ci-policy: ci / gradle-0 (push) Skipped
QRun-IO/ci-policy: ci / node-3 (push) Skipped
QRun-IO/ci-policy: ci / source-config-5 (push) Skipped
QRun-IO/ci-policy: ci / java-maven-2 (push) Skipped
QRun-IO/ci-policy: ci / node-4 (push) Skipped
QRun-IO/ci-policy: ci / not-enrolled (push) Skipped
QBit CodeQL / Analyze branch source (push) Canceled after 0s
CodeQL / analyze (push) Canceled after 0s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / verify (push) Successful in 1m20s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / secrets (push) Successful in 15s
QRun-IO/ci-policy: Shared Java Maven CI with Docker / gate (push) Successful in 8s
QRun-IO/ci-policy: ci / java-docker-1 (push) Successful in 29m37s
QRun-IO/ci-policy: ci / ci-result (push) Successful in 5s
The input form ran before the step that saves the records chosen at process start, so the selection was gone when the form was submitted. The form now comes after that step; a new process test drives it the way a UI does.

Fixes QRun-IO/qqq#1361

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-11-01 05:27:45 -06:00
james.maesandCodex 92760ff091 ci: enroll in the shared QQQ orb (#38)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 19:55:54 -05:00
james.maesandCodex 80bdbff868 ci: use shared orb pipelines and source scanning (#32)
Co-authored-by: Codex <noreply@openai.com>
2026-10-21 10:41:35 -05:00
james.maes 0e61d75f82 chore: migrate license from AGPL-3.0 to Apache-2.0 2026-01-05 10:14:21 -06:00
CircleCI b40a1c2418 Bump version to 0.3.0 [skip ci] 2025-12-28 22:45:26 +00:00
james.maes f346c245ef docs: update README with correct group ID and version 2025-12-28 16:43:59 -06:00
CircleCI 51c1bcf676 Bump version to 0.2.1 [skip ci] 2025-12-28 22:37:37 +00:00
james.maes 7d465d401a feat: Java 21 LTS migration with qbit-build-parent 1.5.0 (#2)
* feat: migrate to Java 21 with qbit-build-parent 1.5.0

* fix: update qbit-build-parent to 1.5.1 for JUnit 6 compatibility

* chore: retrigger CI after Maven Central sync
2025-12-28 16:36:02 -06:00
james.maes 69fc7d0883 docs: Standardize README 2025-12-24 14:20:15 -06:00
james.maes 60a689374b ci: Add GitHub release configuration 2025-12-24 12:44:00 -06:00
CircleCI 7aeceac1b1 Bump version to v0.2.1 [skip ci] 2025-09-16 19:46:33 +00:00
james.maes f59fdb68d1 fix: bump orb version for issue with tag released 2025-09-16 14:45:14 -05:00
11 changed files with 530 additions and 189 deletions

No files matched your search

+10 -53
View File
@@ -1,57 +1,14 @@
version: 2.1
setup: true
orbs:
qqq-orb: kingsrook/qqq-orb@0.3.5
qqq-orb: kingsrook/qqq-orb@0.8
workflows:
test_only:
setup:
jobs:
- qqq-orb/mvn_test_only:
context: [ qqq-maven-registry-credentials ]
filters:
branches:
ignore: /(develop|main|release/.*|hotfix/.*|integration.*)/
tags:
only: []
publish_snapshot:
jobs:
- qqq-orb/mvn_publish:
context: [ qqq-maven-registry-credentials ]
branch_type: snapshot
filters:
branches:
only:
- develop
publish_release_candidate:
jobs:
- qqq-orb/mvn_publish:
context: [ qqq-maven-registry-credentials ]
branch_type: release_candidate
filters:
branches:
only:
- /release\/.*/
publish_release:
jobs:
- qqq-orb/mvn_publish:
context: [ qqq-maven-registry-credentials ]
branch_type: release
filters:
branches:
only: [ main ]
tags:
only: [ /v.*/ ]
publish_hotfix_release:
jobs:
- qqq-orb/mvn_publish:
context: [ qqq-maven-registry-credentials ]
branch_type: hotfix
filters:
branches:
only:
- /hotfix\/.*/
- qqq-orb/generate_pipeline:
orb_version: '0.8'
filters:
branches:
ignore: automation/qbit-rc-pending
tags:
only: /v.*/
+25
View File
@@ -0,0 +1,25 @@
# 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-docker.yaml@qrun-release-v1
with:
runner: linux-small
docker-runner: container-build
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
+31
View File
@@ -0,0 +1,31 @@
# GitHub Release Configuration
# Auto-generates release notes from PR labels
changelog:
exclude:
labels:
- ignore-for-release
- duplicate
- invalid
authors:
- dependabot
- dependabot[bot]
categories:
- title: Breaking Changes
labels:
- breaking-change
- breaking
- title: New Features
labels:
- enhancement
- feature
- title: Bug Fixes
labels:
- bug
- fix
- title: Dependencies
labels:
- dependencies
- title: Other Changes
labels:
- "*"
+23
View File
@@ -0,0 +1,23 @@
name: CodeQL
'on':
push:
branches:
- main
- develop
- release/**
- hotfix/**
pull_request: {}
schedule:
- cron: 41 16 * * 0
workflow_dispatch: {}
permissions:
contents: read
actions: read
security-events: write
packages: read
concurrency:
group: codeql-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
jobs:
analyze:
uses: QRun-IO/qqq-orb/.github/workflows/qbit-codeql.yml@c288a8864fe9054f68410e1d78f5863aea4c4d22
+16
View File
@@ -0,0 +1,16 @@
profile: qbit
orb_version: '0.8'
default_branch: develop
java_version: '21'
browsers: true
api_checks: false
docs_only: false
release_on_main: true
release_tag_policy: v-prefix
test_branch_policy: feature-no-hotfix
publish:
snapshot: true
release_candidate: true
release: true
hotfix: true
asciidoc: false
+202
View File
@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to the Licensor for inclusion in the Work by the copyright
owner or by an individual or Legal Entity authorized to submit on
behalf of the copyright owner. For the purposes of this definition,
"submitted" means any form of electronic, verbal, or written
communication sent to the Licensor or its representatives, including
but not limited to communication on electronic mailing lists, source
code control systems, and issue tracking systems that are managed by,
or on behalf of, the Licensor for the purpose of discussing and
improving the Work, but excluding communication that is conspicuously
marked or otherwise designated in writing by the copyright owner as
"Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2022-2025 QRunIO, LLC
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
http://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.
+15
View File
@@ -0,0 +1,15 @@
qBit: Webhooks
Copyright 2022-2025 QRunIO, LLC
This product includes software developed at QRunIO, LLC (https://qrun.io/).
Licensed under the Apache License, Version 2.0.
================================================================================
Trademarks:
QQQ, qBits, qRun, qStudio, and the QRunIO logo are trademarks of QRunIO, LLC.
Use of these trademarks must comply with QRunIO's Trademark Guidelines.
================================================================================
+106 -133
View File
@@ -1,181 +1,154 @@
# QBit: Webhooks
# qbit-webhooks
[![Version](https://img.shields.io/badge/version-0.1.0-blue.svg)](https://github.com/Kingsrook/qbit-webhooks)
[![License](https://img.shields.io/badge/license-GNU%20Affero%20GPL%20v3-green.svg)](https://www.gnu.org/licenses/agpl-3.0.en.html)
[![Java](https://img.shields.io/badge/java-17+-blue.svg)](https://adoptium.net/)
Outbound webhook notifications for QQQ applications.
> **Webhook Implementation for QQQ Applications - Event-Driven Integration**
**For:** QQQ developers who need to notify external systems when records change
**Status:** Stable
This QBit provides a comprehensive webhook system for QQQ applications, enabling real-time event notifications to external systems and partners.
## Why This Exists
## 🚀 What Makes QBit Webhooks Different?
Modern applications don't exist in isolation. When an order is created, you might need to notify a fulfillment system. When a user updates their profile, a CRM needs to know. Polling for changes is inefficient and adds latency.
**QBit Webhooks gives you enterprise-grade webhook capabilities** - you define your event types, configure webhook endpoints, and QBit Webhooks handles the reliable delivery, retry logic, and comprehensive logging. No more building webhook infrastructure from scratch.
This QBit sends HTTP callbacks when records are inserted, updated, or deleted. Configure endpoints, select which events to send, and the system handles delivery with retries and logging.
### ✨ Core Capabilities
## Features
- **🔗 Webhook Management**: Complete webhook endpoint and subscription management
- **📡 Event Types**: Flexible event type registration and categorization
- **🔄 Reliable Delivery**: Built-in retry logic and delivery tracking
- **📊 Comprehensive Logging**: Full audit trail of webhook events and delivery attempts
- **🎯 Event Filtering**: Subscribe to specific event types and categories
- **📦 JSON Payloads**: Structured event content with API versioning support
- **Event-Driven Delivery** - Fires on insert, update, delete operations
- **Configurable Endpoints** - Register multiple webhook URLs per table
- **Payload Customization** - Include full record, changed fields only, or custom format
- **Retry Logic** - Automatic retries with exponential backoff
- **Delivery Logging** - Track success, failures, and response codes
- **Secret Signing** - HMAC signatures for payload verification
## 🔓 Open Source & Full Control
**QBit Webhooks is 100% open source** - you have complete ownership and control:
- **💻 Your Code**: Full access to QBit Webhooks source code
- **🗄️ Your Data**: All webhook data stays in your systems
- **🏗️ Your System**: Deploy anywhere - on-premises, cloud, or hybrid
- **🔒 No Vendor Lock-in**: No external webhook services required
- **⚡ Full Customization**: Modify and extend webhook behavior to your needs
### Overview
*Note: This is one of the original QBit implementations - so, some of the mechanics of how
it is loaded and used by an application are not exactly fully defined at the time of its
creation... Please excuse any dust or not-quite-round wheels you find here!*
This QBit provides a basic implementation of webhooks to a QQQ Application.
## 🏗️ Architecture
### Technology Stack
- **Java**: Java 17+ with UTF-8 encoding
- **QQQ Framework**: Built on QQQ backend modules
- **Database**: RDBMS support through QQQ tables
- **JSON**: Structured event payloads with API versioning
### What This QBit Contains
- **Webhook Management**: Complete webhook endpoint and subscription system
- **Event Processing**: Event type registration and categorization
- **Delivery System**: Reliable webhook delivery with retry logic
- **Audit Trail**: Comprehensive logging of all webhook activities
- **Database Schema**: Five core tables for webhook management
### What This QBit Does NOT Contain
- **HTTP Client**: Uses QQQ's built-in HTTP capabilities
- **Authentication**: Relies on QQQ's security framework
- **Scheduling**: Uses QQQ's job scheduling system
- **External Services**: No third-party webhook services
## 🚀 Getting Started
## Quick Start
### Prerequisites
- **Java 17+** (required for QQQ features)
- **Maven 3.8+** (for build system)
- **QQQ Application** (this is a QBit, not a standalone application)
- QQQ application (v0.35.0+)
- Database backend configured
### Usage
### Installation
Add to your `pom.xml`:
#### Pom dependency
```xml
<dependency>
<groupId>com.kingsrook.qbits</groupId>
<artifactId>qbit-webhooks</artifactId>
<version>${TODO}</version>
<version>0.3.0</version>
</dependency>
```
#### Setup
##### Define the QBit
### Register the QBit
```java
WebhooksQBitProducer producer = new WebhooksQBitProducer()
.withQBitConfig(new WebhooksQBitConfig());
MetaDataProducerMultiOutput allQBitMetaData = producer.produce(qInstance);
// add it to your QInstance.
public class AppMetaProvider extends QMetaProvider {
@Override
public void configure(QInstance qInstance) {
new WebhooksQBit().configure(qInstance);
}
}
```
##### Define webhook event types
```java
WebhooksRegistry webhooksRegistry = WebhooksRegistry.ofOrWithNew(qInstance);
### Configure a Webhook
webhooksRegistry.registerWebhookEventType(new WebhookEventType()
.withName(ORDER_CREATED_EVENT_TYPE)
.withLabel("Order Created")
.withCategory(WebhookEventCategory.INSERT)
.withTableName(Order.TABLE_NAME));
```java
new QWebhookMetaData()
.withName("orderCreatedNotification")
.withTable("order")
.withEvents(WebhookEvent.INSERT)
.withUrl("https://api.example.com/webhooks/orders")
.withSecret("your-signing-secret");
```
### Provides
#### Tables
- `webhook` - a connection to a partner system, e.g., URL to post events to.
- `webhookSubscription` - which event types should be sent to a webhook.
- `webhookEvent` - table-as-queue plus log of events for sending to a subscription.
- `webhookEventContent` - storage of JSON bodies to post.
- `webhookEventSendLog` - tracking of attempted sends.
## Usage
#### Classes
- `WebhooksRegistry` - where application-defined event types must be registered.
### Multiple Events
### Dependencies
- `qqq-backend-module-api` - as the objects posted are api-versioned
- `QQQTablesMetaDataProvider` - for event record foreign keys
```java
new QWebhookMetaData()
.withName("customerSync")
.withTable("customer")
.withEvents(WebhookEvent.INSERT, WebhookEvent.UPDATE, WebhookEvent.DELETE)
.withUrl("https://crm.example.com/sync");
```
## 📚 Documentation
### Conditional Delivery
**📖 [Complete Documentation Wiki](https://github.com/Kingsrook/qqq/wiki)** - Start here for comprehensive guides
```java
// Only fire when status changes to 'approved'
new QWebhookMetaData()
.withName("approvalNotification")
.withTable("order")
.withEvents(WebhookEvent.UPDATE)
.withCondition((oldRecord, newRecord) ->
"approved".equals(newRecord.getValue("status")) &&
!"approved".equals(oldRecord.getValue("status")))
.withUrl("https://notify.example.com/approved");
```
- **[🏠 Home](https://github.com/Kingsrook/qqq/wiki/Home)** - Project overview and quick start
- **[🏗️ Architecture](https://github.com/Kingsrook/qqq/wiki/High-Level-Architecture)** - System design and principles
- **[🔧 Development](https://github.com/Kingsrook/qqq/wiki/Developer-Onboarding)** - Setup and contribution guide
- **[📦 Modules](https://github.com/Kingsrook/qqq/wiki/Core-Modules)** - Available components and usage
- **[🚀 Building](https://github.com/Kingsrook/qqq/wiki/Building-Locally)** - Local development workflow
- **[🔌 QBits](https://github.com/Kingsrook/qqq/wiki/QBit-Development)** - QBit development guide
### Custom Payload
## 🤝 Contributing
```java
new QWebhookMetaData()
.withName("slackNotification")
.withTable("alert")
.withEvents(WebhookEvent.INSERT)
.withPayloadTransformer((record) -> Map.of(
"text", "New alert: " + record.getValue("message"),
"channel", "#alerts"
))
.withUrl("https://hooks.slack.com/services/xxx");
```
QBit Webhooks is **open source** and welcomes contributions!
### Verifying Signatures
- **🐛 [Report Issues](https://github.com/Kingsrook/qqq/issues)** - Bug reports and feature requests
- **📝 [Contribution Guide](https://github.com/Kingsrook/qqq/wiki/Contribution-Guidelines)** - How to contribute code and documentation
- **🔍 [Code Standards](https://github.com/Kingsrook/qqq/wiki/Code-Review-Standards)** - QQQ's coding standards and review process
Receiving systems can verify the payload using the HMAC-SHA256 signature in the `X-Webhook-Signature` header:
**First time contributing?** Start with our [Developer Onboarding Guide](https://github.com/Kingsrook/qqq/wiki/Developer-Onboarding) to get your environment set up.
```java
String payload = requestBody;
String signature = request.getHeader("X-Webhook-Signature");
String expected = HmacUtils.hmacSha256Hex(secret, payload);
if (!expected.equals(signature)) {
throw new SecurityException("Invalid signature");
}
```
## 🏢 About Kingsrook
## Configuration
QBit Webhooks is built by **[Kingsrook](https://qrun.io)** - making engineers more productive through intelligent automation and developer tools.
The QBit creates these tables:
- **Website**: [https://qrun.io](https://qrun.io)
- **Contact**: [contact@kingsrook.com](mailto:contact@kingsrook.com)
- **GitHub**: [https://github.com/Kingsrook](https://github.com/Kingsrook)
| Table | Purpose |
|-------|---------|
| `webhook` | Webhook endpoint configurations |
| `webhook_log` | Delivery attempts and responses |
## 📄 License
### Retry Settings
This project is licensed under the **GNU Affero General Public License v3.0** - see the [LICENSE.txt](LICENSE.txt) file for details.
```java
new WebhooksQBit()
.withMaxRetries(5)
.withRetryDelayMs(1000)
.withRetryBackoffMultiplier(2.0);
```
## 🆘 Support & Community
## Project Status
### ⚠️ Important: Use Main QQQ Repository
Stable and production-ready.
**All support, issues, discussions, and community interactions should go through the main QQQ repository:**
### Roadmap
- **Main Repository**: https://github.com/Kingsrook/qqq
- **Issues**: https://github.com/Kingsrook/qqq/issues
- **Discussions**: https://github.com/Kingsrook/qqq/discussions
- **Wiki**: https://github.com/Kingsrook/qqq.wiki
- Batch delivery (group multiple events)
- Dead letter queue for failed deliveries
- Dashboard UI for webhook management
### Why This Repository Exists
## Contributing
This repository is maintained separately from the main QQQ repository to:
- **Enable independent QBit development** and versioning
- **Allow QBit-specific CI/CD** and deployment pipelines
- **Provide clear separation** between QBit components and core framework concerns
- **Support different release cycles** for QBits vs. core framework
1. Fork the repository
2. Create a feature branch
3. Run tests: `mvn clean verify`
4. Submit a pull request
### Getting Help
- **Documentation**: Check the [QQQ Wiki](https://github.com/Kingsrook/qqq.wiki)
- **Issues**: Report bugs and feature requests on [Main QQQ Issues](https://github.com/Kingsrook/qqq/issues)
- **Discussions**: Join community discussions on [Main QQQ Discussions](https://github.com/Kingsrook/qqq/discussions)
- **Questions**: Ask questions in the main QQQ repository
---
**Ready to integrate webhooks into your QQQ application?** [Get started with QBit Webhooks today!](https://github.com/Kingsrook/qqq/wiki/QBit-Development)
## License
Proprietary - QRun.IO
+2 -2
View File
@@ -26,7 +26,7 @@
<parent>
<groupId>com.kingsrook</groupId>
<artifactId>qbit-build-parent</artifactId>
<version>1.1.0</version>
<version>1.5.1</version>
</parent>
<groupId>com.kingsrook.qbits</groupId>
@@ -64,7 +64,7 @@
</scm>
<properties>
<revision>0.2.0-SNAPSHOT</revision>
<revision>0.3.0</revision>
</properties>
@@ -73,7 +73,15 @@ public class SendTestEventToWebhookProcessMetaDataProducer extends MetaDataProdu
))
.getProcessMetaData();
processMetaData.withStep(0, new QFrontendStepMetaData()
//////////////////////////////////////////////////////////////////////////
// the input screen goes after the first backend (preview) step, not //
// before it: the records chosen when the process starts reach only //
// the steps of that first run, and the preview step is what keeps //
// them (as queryFilterJson) for the later steps. As step 0 the screen //
// stopped the first run before any backend step, so the records were //
// lost and the extract failed with "No records were selected". //
//////////////////////////////////////////////////////////////////////////
processMetaData.withStep(1, new QFrontendStepMetaData()
.withName("input")
.withComponent(new QFrontendComponentMetaData()
.withType(QComponentType.EDIT_FORM))
@@ -0,0 +1,91 @@
/*
* QQQ - Low-code Application Framework for Engineers.
* Copyright (C) 2021-2025. Kingsrook, LLC
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
* contact@kingsrook.com
* https://github.com/Kingsrook/
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.kingsrook.qbits.webhooks.processes;
import java.util.List;
import com.kingsrook.qbits.webhooks.BaseTest;
import com.kingsrook.qqq.backend.core.actions.processes.QProcessCallback;
import com.kingsrook.qqq.backend.core.actions.processes.RunProcessAction;
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.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 org.junit.jupiter.api.Test;
import static org.assertj.core.api.Assertions.assertThat;
/*******************************************************************************
** Runs SendTestEventToWebhook the way a UI does: records are given only when
** the process starts (the init call); the continuation of the first screen
** carries no records.
*******************************************************************************/
class SendTestEventToWebhookProcessTest extends BaseTest
{
/*******************************************************************************
** The records chosen at init must still be there after the input screen.
*******************************************************************************/
@Test
void testRecordsFromInitSurviveTheInputScreen() throws QException
{
Integer webhookId = insert(newWebhook("Test receiver"));
///////////////////////////////////////////////////////////////////////
// init: records come in through the callback, as the UI API's init //
// passes recordIds; the run stops at the first frontend step //
///////////////////////////////////////////////////////////////////////
RunProcessInput initInput = new RunProcessInput();
initInput.setProcessName(SendTestEventToWebhookProcessMetaDataProducer.NAME);
initInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK);
initInput.setCallback(new QProcessCallback()
{
@Override
public QQueryFilter getQueryFilter()
{
return (new QQueryFilter(new QFilterCriteria("id", QCriteriaOperator.IN, List.of(webhookId))));
}
});
RunProcessOutput initOutput = new RunProcessAction().execute(initInput);
assertThat(initOutput.getException()).isEmpty();
assertThat(initOutput.getProcessState().getNextStepName()).contains("input");
//////////////////////////////////////////////////////////////////////
// continue after the input screen, with no callback: the extract //
// must still find the webhook chosen at init //
//////////////////////////////////////////////////////////////////////
RunProcessInput stepInput = new RunProcessInput();
stepInput.setProcessName(SendTestEventToWebhookProcessMetaDataProducer.NAME);
stepInput.setProcessUUID(initOutput.getProcessUUID());
stepInput.setStartAfterStep("input");
stepInput.setFrontendStepBehavior(RunProcessInput.FrontendStepBehavior.BREAK);
stepInput.addValue("uponSuccessUpdateUnhealthyWebhooksToProbation", true);
RunProcessOutput stepOutput = new RunProcessAction().execute(stepInput);
assertThat(stepOutput.getException()).isEmpty();
assertThat(stepOutput.getProcessState().getNextStepName()).contains("review");
assertThat(stepOutput.getValues().get("recordCount")).isEqualTo(1);
}
}