Compare commits

...
Author SHA1 Message Date
CircleCI 94bed610cd Bump version to 0.1.0-RC.8 [skip ci] 2026-10-16 16:01:11 +00:00
github-actions[bot] 5d863aed12 Merge develop into release/0.1 for the next 0.1.0 RC 2026-10-16 15:58:16 +00:00
james.maes 68137d6fff Merge pull request #28 from QRun-IO/fix/email-health-tile-wording
fix(email): worded health tile instead of raw UNKNOWN
2026-10-16 10:20:09 -05:00
james.maesandClaude Opus 5.5 ba033f90bc fix(email): worded health tile instead of the raw UNKNOWN enum
The Email health tile showed the HealthStatus name (UP/DEGRADED/DOWN/UNKNOWN). It now shows Healthy / Degraded / Down, and "Not checked yet" when no
check has reported, with the check cadence in the context line. The drill to health_check is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-16 09:13:24 -05:00
james.maes 33b70be266 Merge pull request #26 from QRun-IO/ci/release-rc-secrets-inherit
ci(release-rc): pass secrets so the shared workflow can sign the RC tag
2026-10-16 08:04:53 -05:00
james.maesandClaude Opus 5.5 d11e38139c ci(release-rc): pass secrets to the shared workflow so it can sign the RC tag
QRun-IO/.github#2 adds a tag-and-release job that signs vX with the
QRUN_TAG_SIGNING_KEY secret after Maven Central confirms the artifact.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 16:20:34 -05:00
james.maes a9415f93d9 Merge pull request #25 from QRun-IO/fix/producer-version-from-build
fix: qbit metadata version comes from the build
2026-10-15 01:42:56 -05:00
james.maesandClaude Opus 5.5 50f31a9040 fix: the qbit metadata version comes from the build, not a hardcoded constant
The producer read a literal version, so released artifacts reported a stale
or -SNAPSHOT version. qbit-email.properties (version=${project.version}) is
Maven-filtered and the producer loads it; tests assert producer == bundle ==
artifact version.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:24:08 -05:00
CircleCI 1051f1780d Bump version to 0.1.0-RC.7 [skip ci] 2026-10-15 06:22:43 +00:00
james.maesandClaude Opus 5.5 03c813583c Merge develop into release/0.1 for 0.1.0-RC.7 (#20 help bundle, #16 health, #22, #23 bundle version)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 01:18:16 -05:00
james.maes 640e37f7c2 Merge pull request #23 from QRun-IO/fix/bundle-version-assertion
fix(test): compare the help bundle version with the qbit version
2026-10-15 01:14:50 -05:00
james.maesandClaude Opus 5.5 5bfd7f21e4 Merge develop into release/0.1 for 0.1.0-RC.7 (#20 help bundle)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:30:02 -05:00
CircleCI 5a77659e85 Bump version to 0.1.0-RC.6 [skip ci] 2026-10-14 23:10:05 +00:00
james.maesandClaude Opus 5.5 e1c0d1f7df Merge develop into release/0.1 for 0.1.0-RC.6 (#17 indexes and FKs)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 18:05:04 -05:00
CircleCI f6b26737ae Bump version to 0.1.0-RC.5 [skip ci] 2026-10-14 22:39:08 +00:00
james.maesandClaude Opus 5.5 2e516c6f0f Merge develop into release/0.1 for 0.1.0-RC.5 (#19 brand seed, Setup card)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:34:48 -05:00
CircleCI 5f40c93727 Bump version to 0.1.0-RC.4 [skip ci] 2026-10-14 21:52:46 +00:00
james.maesandClaude Opus 5.5 12a17808a8 Merge develop into release/0.1 for 0.1.0-RC.4 (#18 EmailJobEffectTarget, QQQ RC.7)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:48:00 -05:00
CircleCI f495dcc9ae Bump version to 0.1.0-RC.3 [skip ci] 2026-10-14 15:19:50 +00:00
james.maesandClaude Opus 5.5 040fb3e35c Merge develop into release/0.1 for 0.1.0-RC.3 (#15 readiness checks)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 10:16:28 -05:00
CircleCI a087e8b1f3 Bump version to 0.1.0-RC.2 [skip ci] 2026-10-14 14:48:15 +00:00
james.maesandClaude Opus 5.5 24c936453f Merge develop into release/0.1 for 0.1.0-RC.2 (#12 Message-ID and sendOnce, #13 public api)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 09:44:17 -05:00
CircleCI 1ff27e90be Bump version to 0.1.0-RC.1 [skip ci] 2026-10-14 12:38:05 +00:00
9 changed files with 88 additions and 11 deletions

No files matched your search

+1
View File
@@ -21,5 +21,6 @@ jobs:
release-rc:
if: github.event_name == 'workflow_dispatch' || (github.event.pull_request.merged == true && contains(github.event.pull_request.labels.*.name, 'release:rc'))
uses: QRun-IO/.github/.github/workflows/qbit-release-rc.yml@main
secrets: inherit
with:
dry_run: ${{ github.event_name == 'workflow_dispatch' && inputs.dry_run }}
+1
View File
@@ -17,6 +17,7 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
- `EmailTablesGuardrailTest`: every table is used by runtime code, seeded or listed as starting empty with a reason, and has help.
### Fixed
- Derive the qbit metadata version from the Maven artifact version so released artifacts report their release version.
- `email_template.ownerQbit` was stored but never read, and the built-in template did not set it. The built-in now names `qbit-email`, the field is read-only with help, and messages sent from a template name its owner qbit as `senderQbit` unless the request names one.
### Added
+12 -1
View File
@@ -22,7 +22,7 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<properties>
<revision>0.1.0-SNAPSHOT</revision>
<revision>0.1.0-RC.8</revision>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
<jacoco.version>0.8.15</jacoco.version>
@@ -175,6 +175,7 @@
<filtering>true</filtering>
<includes>
<include>help/*.yaml</include>
<include>qbit-email.properties</include>
</includes>
</resource>
<resource>
@@ -182,10 +183,20 @@
<filtering>false</filtering>
<excludes>
<exclude>help/*.yaml</exclude>
<exclude>qbit-email.properties</exclude>
</excludes>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<systemPropertyVariables combine.children="append">
<qbit.artifactVersion>${project.version}</qbit.artifactVersion>
</systemPropertyVariables>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-checkstyle-plugin</artifactId>
@@ -21,10 +21,13 @@
package com.kingsrook.qbits.email;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import com.kingsrook.qbits.email.app.EmailApp;
import com.kingsrook.qbits.email.bridge.EmailOutboxMessagingProviderMetaData;
import com.kingsrook.qbits.email.delivery.EmailAlerts;
@@ -53,13 +56,41 @@ public class EmailQBitProducer implements QBitMetaDataProducer<EmailQBitConfig>
{
public static final String GROUP_ID = "com.kingsrook.qbits";
public static final String ARTIFACT_ID = "qbit-email";
public static final String VERSION = "0.1.0";
public static final String VERSION = loadVersion();
private EmailQBitConfig qBitConfig;
private String namespace;
/*******************************************************************************
** Read the artifact version from the Maven-filtered resource.
*******************************************************************************/
private static String loadVersion()
{
try(InputStream inputStream = EmailQBitProducer.class.getClassLoader().getResourceAsStream("qbit-email.properties"))
{
if(inputStream == null)
{
throw new IllegalStateException("Missing qbit-email.properties; build with Maven to generate the artifact version");
}
Properties properties = new Properties();
properties.load(inputStream);
String version = properties.getProperty("version");
if(version == null || version.isBlank() || version.contains("${"))
{
throw new IllegalStateException("Missing or unfiltered version in qbit-email.properties; build with Maven");
}
return (version);
}
catch(IOException e)
{
throw new IllegalStateException("Could not read artifact version from qbit-email.properties", e);
}
}
/*******************************************************************************
** Fluent setter for the config.
*******************************************************************************/
@@ -102,7 +102,7 @@ public class EmailHealthChecks implements HealthCheckSupplier
{
return (new HealthCheckMetaData().withName(name).withLabel(name).withComponent("email")
.withCategory(HealthCategory.EMAIL).withKind(HealthCheckKind.DEPENDENCY)
.withIntervalSeconds(300).withTimeoutMs(5000).withCheckInstance(input ->
.withIntervalSeconds(EmailHealthWidget.CHECK_INTERVAL_MINUTES * 60).withTimeoutMs(5000).withCheckInstance(input ->
{
try
{
@@ -47,6 +47,31 @@ import com.kingsrook.qqq.backend.core.utils.JsonUtils;
*******************************************************************************/
public class EmailHealthWidget extends AbstractWidgetRenderer
{
public static final String NOT_CHECKED = "Not checked yet";
//////////////////////////////////////////////////////////////////////////////
// the interval of the email health checks (EmailHealthChecks), in minutes //
//////////////////////////////////////////////////////////////////////////////
static final int CHECK_INTERVAL_MINUTES = 5;
/*******************************************************************************
** Human words for a health status; unknown means no check has reported yet.
*******************************************************************************/
static String wording(String status)
{
return (switch(status)
{
case "UP" -> "Healthy";
case "DEGRADED" -> "Degraded";
case "DOWN" -> "Down";
default -> NOT_CHECKED;
});
}
/*******************************************************************************
** Read the persisted rollup as the viewer, preserving health permissions.
*******************************************************************************/
@@ -72,8 +97,8 @@ public class EmailHealthWidget extends AbstractWidgetRenderer
}
}
}
StatisticsData data = new StatisticsData().withCount(status).withCountFontSize("1rem")
.withCountContext(!installed ? "Email - health monitoring not configured" : readable ? "Email - System Health" : "Email - health access required");
String context = !installed ? "Email - health monitoring not configured" : readable ? "Email - System Health. Checks run every " + CHECK_INTERVAL_MINUTES + " minutes" : "Email - health access required";
StatisticsData data = new StatisticsData().withCount(wording(status)).withCountFontSize("1rem").withCountContext(context);
if(readable)
{
data.withCountURL("/app/health_check?filter=" + URLEncoder.encode(JsonUtils.toJson(EmailStore.equal("component", "email")), StandardCharsets.UTF_8));
+1
View File
@@ -0,0 +1 @@
version=${project.version}
@@ -48,13 +48,17 @@ class EmailQBitProducerTest
assertThat(producer.isEnabled()).isTrue();
assertThat(producer.getNamespace()).isNull();
assertThat(producer.getQBitConfig().getBackendName()).isEqualTo("memory");
String artifactVersion = System.getProperty("qbit.artifactVersion");
assertThat(artifactVersion).isNotBlank();
assertThat(EmailQBitProducer.VERSION).isEqualTo(artifactVersion);
assertThat(producer.getQBitMetaData().getVersion()).isEqualTo(artifactVersion);
assertThat(producer.produce(instance)).isNotNull();
assertThat(instance.getQBits()).isNotEmpty();
assertThat(instance.getTable("helpContent")).isNull();
assertThat(HelpBundles.of(instance).getBundles()).singleElement().satisfies(bundle ->
{
assertThat(bundle.getId()).isEqualTo("qbit-email");
assertThat(bundle.getVersion()).matches("\\d+\\.\\d+\\.\\d+(-SNAPSHOT|-RC\\.\\d+)?");
assertThat(bundle.getVersion()).isEqualTo(artifactVersion).isEqualTo(EmailQBitProducer.VERSION);
});
}
@@ -62,7 +62,7 @@ class EmailHealthWidgetTest
var instance = H2Support.setup(config -> config.withSecretResolver(name -> null));
assertThat(instance.getWidget("emailHealth").getType()).isEqualTo(WidgetType.STATISTICS.getType());
var data = render();
assertThat(data.getCount()).isEqualTo("UNKNOWN");
assertThat(data.getCount()).isEqualTo("Not checked yet");
assertThat(data.getCountContext()).contains("not configured");
assertThat(data.getCountURL()).isNull();
}
@@ -88,15 +88,18 @@ class EmailHealthWidgetTest
assertThat(render().getCountURL()).isNull();
QContext.getQSession().setPermissions(Set.of("email.view", "health.read"));
instance.setEnforcePermissionsInCoreActions(true);
assertThat(render().getCount()).isEqualTo("UNKNOWN");
assertThat(render().getCount()).isEqualTo("Not checked yet");
assertThat(render().getCountContext()).contains("Checks run every 5 minutes");
connection.createStatement().execute("INSERT INTO health_component VALUES (1, 'email', 'DOWN'), (2, 'other', 'UP')");
assertThat(render().getCount()).isEqualTo("DOWN");
assertThat(render().getCount()).isEqualTo("Down");
String link = URLDecoder.decode(render().getCountURL(), StandardCharsets.UTF_8);
assertThat(link).startsWith("/app/health_check?filter=").contains("component", "email");
connection.createStatement().execute("UPDATE health_component SET status = 'UP' WHERE id = 1");
assertThat(render().getCount()).isEqualTo("UP");
assertThat(render().getCount()).isEqualTo("Healthy");
connection.createStatement().execute("UPDATE health_component SET status = 'DEGRADED' WHERE id = 1");
assertThat(render().getCount()).isEqualTo("Degraded");
connection.createStatement().execute("UPDATE health_component SET status = 'S3NT1NEL-secret' WHERE id = 1");
assertThat(render().getCount()).isEqualTo("UNKNOWN");
assertThat(render().getCount()).isEqualTo("Not checked yet");
assertThat(render().getCountContext()).doesNotContain("S3NT1NEL-secret");
}
}