diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b8ca11..ab95dea 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,7 +5,13 @@ All notable changes to this project are documented here. The format follows Keep
## [0.1.0] - Unreleased
### Changed
-- Build against the released QQQ 4.1.0-RC.5 BOM. Alert types and sink registration now use core's alerts SPI; host `QAlerts.raise/resolve/heartbeat` calls reach the installed qbit through core instance metadata (qbit-alerts#15, F3).
+- Target QQQ `4.1.0-RC.6` for the UI extension API; local verification uses a command-line version override only.
+- Register six JSON UI specs as extension `alerts` at the established `/alerts` prefix, preserving methods, paths and existing payload fields. Publish the OpenAPI fragment with a regeneration/contract test and strict server coverage (#13, F2).
+- Add summary `inboxHref`, notification operation actions, alert process actions with complete hrefs, and record-panel `follow.available` (#13, F20). Follow discovery reads core extension metadata without a collaboration dependency or URL.
+- Remove the record-panel watch route and handler. Existing RECORD subscriptions and the deprecated `watching` response remain readable without a migration; new panel follows belong to collaboration. Follower fan-out waits for core #1164 (#16, F12).
+- Move Alertmanager ingest to `/qqq/hooks/alerts/alertmanager`. The old path is an opt-in alias (`legacyAlertmanagerPath`, default false) for one minor release, incompatible with strict raw-route mode. Each alias call carries deprecation/sunset headers and logs a warning; authentication is shared (#14, F7).
+- Run Checkstyle 14.3.0 in Maven validate (including tests); align the license template with this repository's existing headers and correct pre-existing switch/import formatting so the style check passes.
+- Alert types and sink registration use core's alerts SPI (introduced in QQQ 4.1.0-RC.5); host `QAlerts.raise/resolve/heartbeat` calls reach the installed qbit through core instance metadata (qbit-alerts#15, F3).
- Removed alert SPI copies. Retained `spi.NotificationSignal`, `NotificationAudience`, `NotificationCategory`, `NotificationSeverity`, `AlertLink`, and the binary-compatible `AlertsRuntime.sink().notify(...)` entry point for qbit-easypost-tracking 0.1.x until QRun-IO/qqq#1125 ships in RC.6.
- Webhook signing delegates to core `WebhookSigner`/`WebhookSecret` (qbit-alerts#15, F11). Headers, `v1,` signatures and `whsec_` encoding are unchanged. A thin adapter preserves legacy keys shorter than core's 16-byte minimum using equivalent HMAC zero-padding.
@@ -16,7 +22,7 @@ All notable changes to this project are documented here. The format follows Keep
- a generic webhook, signed with Standard Webhooks headers through core's `WebhookSigner` (QRun-IO/qqq#1109)
- PagerDuty (Events API v2: trigger, acknowledge and resolve)
- an Alertmanager sink (`/api/v2/alerts`, with the qqq labels and the origin guard)
-- Prometheus Alertmanager ingest at `POST /qqq/v1/alerts/ingest/alertmanager`. It takes a bearer token, creates EXTERNAL types with notifications off, and keeps a loop guard.
+- Prometheus Alertmanager ingest at `POST /qqq/hooks/alerts/alertmanager`. It takes a bearer token, creates EXTERNAL types with notifications off, and keeps a loop guard.
- Tables (`alerts_*`), with the Liquibase changelog `db/liquibase/qbit-alerts/alerts.changelog.sql`:
- alerts, their timeline and records
- types, groups and monitors
diff --git a/README.md b/README.md
index ed898d3..65fd968 100644
--- a/README.md
+++ b/README.md
@@ -26,7 +26,7 @@ Any qbit or app reports exceptional conditions ("a person should do something").
- Prometheus Alertmanager, as a sink
Any channel another qbit registers works too. Every suppressed notification is logged with its reason.
-- **Ingests Prometheus Alertmanager** at `POST /qqq/v1/alerts/ingest/alertmanager`, with notifications off for those types, so infrastructure alerts are triaged in one place.
+- **Ingests Prometheus Alertmanager** at `POST /qqq/hooks/alerts/alertmanager`, with notifications off for those types, so infrastructure alerts are triaged in one place.
- **Owns the Alerts home:** open alerts by severity, my alerts, needs action, by group, a 30-day trend, the noisiest types, delivery health and recently resolved. Other pages get link-only summaries from `AlertSummaryLoader`.
All data is in standard QQQ tables named `alerts_*`. People change alerts through record actions, never by editing rows.
@@ -48,10 +48,26 @@ Grant `getNotificationAdminPermissionNames()` to authors. Viewers' permissions i
**Upgrading from 0.1.0-RC.1:** changeset 16 renames the delivery log `alerts_notification` to `alerts_delivery`, and changeset 17 drops `alerts_inbox_item` (bell state only). Both are guarded, so a fresh schema and an RC.1 schema both end up the same.
+The UI routes are published as the `alerts` extension (`1.0.0`, established prefix `/alerts`), with
+[the OpenAPI fragment](src/main/resources/openapi/qqq-v1/alerts.yaml) and a contract test.
+See [UI API and webhook migration](docs/UI-API.md) for operation IDs and the complete Next handoff.
+Summary `inboxHref`, item/alert `actions`, and panel `follow.available` are additive. Existing
+hrefs, `notificationsHref`, `alertsHref`, `canManage` and deprecated `watching` remain.
+
+Record following belongs to collaboration: `POST /alerts/record-panel/watch` is removed. Old
+RECORD subscriptions remain readable without migration. The panel creates no subscriptions;
+Next uses its collaboration module when `follow.available` is true. Follower fan-out waits
+for core QRun-IO/qqq#1164.
+
+The old Alertmanager path is available for one minor release only via
+`withLegacyAlertmanagerPath(true)` (default false). It requires non-strict UI route mode,
+returns `Deprecation: true` and `Sunset: Tue, 13 Apr 2027 00:00:00 GMT` on every call, and logs
+a warning. Both paths use the same bearer-token check. Strict hosts use the canonical hook.
+
## Requirements
- Java 21.
-- QQQ 4.1 (built against `4.1.0-RC.5`).
+- QQQ 4.1 (targets `4.1.0-RC.6`; release pending).
- An RDBMS backend.
- A scheduler, for monitors and email delivery.
- A messaging provider, for email.
@@ -95,7 +111,7 @@ QAlerts.raise(new AlertSignal("cdn.purge.dead")
`QAlerts.resolve(...)` and `QAlerts.heartbeat(...)` reach the same sink. Declare types with core's `QAlertTypeMetaData`. Calls through `QAlerts.raise/resolve` defer to transaction commit when a transaction is present; without this qbit installed, core uses its logging sink.
-User notifications remain on `AlertsRuntime.get(qInstance).sink().notify(...)`: core's notification API ([QRun-IO/qqq#1125](https://github.com/QRun-IO/qqq/pull/1125)) is expected in RC.6 and is not in RC.5. The four `spi.Notification*` types and `spi.AlertLink` retain their binary signatures for released qbit-easypost-tracking 0.1.x. Use core `AlertLink` for alert signals and `spi.AlertLink` for notification signals until that migration.
+User notifications remain on `AlertsRuntime.get(qInstance).sink().notify(...)`: core's notification API ([QRun-IO/qqq#1125](https://github.com/QRun-IO/qqq/pull/1125)) ships in RC.6; wiring the retained notification adapter to that API is a separate migration. The four `spi.Notification*` types and `spi.AlertLink` retain their binary signatures for released qbit-easypost-tracking 0.1.x. Use core `AlertLink` for alert signals and `spi.AlertLink` for notification signals until that migration.
## Endpoints (for the Next UI)
@@ -107,8 +123,7 @@ User notifications remain on `AlertsRuntime.get(qInstance).sink().notify(...)`:
| POST | `/qqq/v1/alerts/inbox/unread` | `{"id": n}` or `{"ids": [..]}` |
| POST | `/qqq/v1/alerts/inbox/dismiss` | `{"id": n}`, `{"ids": [..]}` or `{"all": true}` |
| GET | `/qqq/v1/alerts/record-panel?tableName=t&recordId=id` | The "Open alerts" panel for a record (`visible: false` when there is nothing to show) |
-| POST | `/qqq/v1/alerts/record-panel/watch` | `{"tableName": t, "recordId": id}`: a RECORD subscription for the user |
-| POST | `/qqq/v1/alerts/ingest/alertmanager` | Alertmanager's webhook receiver (version 4). It takes a bearer token whose reference is set by `withAlertmanagerIngestTokenSecretRef`, and is off without one. |
+| POST | `/qqq/hooks/alerts/alertmanager` | Alertmanager's webhook receiver (version 4). It takes a bearer token whose reference is set by `withAlertmanagerIngestTokenSecretRef`, and is off without one. |
## External channels
diff --git a/checkstyle/config.xml b/checkstyle/config.xml
index 385d653..9419bf2 100644
--- a/checkstyle/config.xml
+++ b/checkstyle/config.xml
@@ -240,7 +240,7 @@
-
+
diff --git a/checkstyle/license.txt b/checkstyle/license.txt
index 92332b5..4cbecc1 100644
--- a/checkstyle/license.txt
+++ b/checkstyle/license.txt
@@ -1,15 +1,11 @@
/*
- * 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/
+ * Copyright 2026 Kingsrook, 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
*
- * https://www.apache.org/licenses/LICENSE-2.0
+ * 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,
diff --git a/docs/DESIGN.md b/docs/DESIGN.md
index b46a540..99c8eae 100644
--- a/docs/DESIGN.md
+++ b/docs/DESIGN.md
@@ -3,6 +3,7 @@
The design for this qbit is tracked in [QRun-IO/qqq#1090](https://github.com/QRun-IO/qqq/pull/1090). This repository implements it. Phases P1 to P3 are built (see README.md); P4 to P6 remain.
Deviations from the design, and why:
+- **UI API and record following:** the RC.6 alerts extension, collaboration ownership of follow, and machine webhook migration are specified in [UI-API.md](UI-API.md). Existing RECORD watch subscriptions remain readable without migration.
- **Alerts use core's RC.5 SPI** (QRun-IO/qqq#1108), including sink registration in core instance metadata. Only notification types and their `AlertLink` signature dependency remain in `spi/`, preserving released producers until QRun-IO/qqq#1125 ships in RC.6.
- **Visibility checks 1 to 3 (section 7.2)** are enforced by record security locks on session keys (`alerts_permission`, `alerts_readable_table`), not by a pre-query customizer. QQQ has no pre-query customizer, and locks also apply to counts, exports and the API.
- **Heartbeats** are stored in an extra table, `alerts_heartbeat`.
diff --git a/docs/NOTIFICATIONS.md b/docs/NOTIFICATIONS.md
index a739ceb..ad1e72a 100644
--- a/docs/NOTIFICATIONS.md
+++ b/docs/NOTIFICATIONS.md
@@ -139,8 +139,9 @@ notification admins. The home stays the only Alerts dashboard.
## 6. Producer SPI
The notification API below is planned in [QRun-IO/qqq#1125](https://github.com/QRun-IO/qqq/pull/1125)
-for 4.1.0-RC.6. The baseline is 4.1.0-RC.5, which ships the core alert SPI
-(`com.kingsrook.qqq.backend.core.alerts`) but does **not** yet provide `QAlerts.notify`:
+for 4.1.0-RC.6. This branch now targets 4.1.0-RC.6 for the UI extension API. The retained qbit notification
+adapter still needs a separate migration to core `QAlerts.notify`; the following remains the
+target integration shape:
```java
QAlerts.notify(new NotificationSignal("Your export failed")
@@ -250,3 +251,9 @@ recipients) whose `expireAt` is more than 7 days past, plus SENT or RETRACTED no
5. Core authentication events? **Later.** Hosts call `AlertsSecurityNotices` from their authentication
customizer until core offers a sign-in event.
6. Server-sent events? **Later.** Polling with backoff is enough at 60 s.
+
+## UI contract
+
+The implemented UI routes and additive action/follow metadata are specified in [UI-API.md](UI-API.md),
+with the generated fragment at `src/main/resources/openapi/qqq-v1/alerts.yaml`. Record following
+belongs to collaboration; the old alerts panel watch route is removed.
diff --git a/docs/UI-API.md b/docs/UI-API.md
new file mode 100644
index 0000000..2a62c71
--- /dev/null
+++ b/docs/UI-API.md
@@ -0,0 +1,108 @@
+# Alerts UI API and webhook migration
+
+The qbit targets QQQ **4.1.0-RC.6** (pending release). `AlertsQBitProducer` registers the
+`alerts` `MiddlewareSpecExtension`, version `1.0.0`, with the established `/alerts` prefix.
+`withRegisterRoutes(false)` opts out of both the extension and webhook registration.
+The six JSON specs extend `AbstractJsonEndpointSpec`; existing `AlertsApi` logic continues
+to enforce caller permissions, recipient ownership and alert visibility.
+
+The deterministic fragment is `src/main/resources/openapi/qqq-v1/alerts.yaml`. Next should
+vendor it. The live `/qqq/v1/openapi.yaml` includes these operations, and `/qqq/v1/version`
+discovers the `alerts` extension so clients need not infer installation from an app name.
+
+| Method | Path | operationId |
+| --- | --- | --- |
+| GET | `/qqq/v1/alerts/inbox/summary` | `alertsInboxSummary` |
+| GET | `/qqq/v1/alerts/inbox` | `alertsInbox` |
+| POST | `/qqq/v1/alerts/inbox/read` | `alertsInboxRead` |
+| POST | `/qqq/v1/alerts/inbox/unread` | `alertsInboxUnread` |
+| POST | `/qqq/v1/alerts/inbox/dismiss` | `alertsInboxDismiss` |
+| GET | `/qqq/v1/alerts/record-panel` | `alertsRecordPanel` |
+
+All existing query parameters, selector precedence, paging defaults and clamps, status
+codes, JSON content-type requirements, and response keys remain. IDs refer to recipient
+rows for inbox operations; legacy `alertId` selectors remain accepted. `all:true` applies
+only to read/dismiss. Items in a summary omit `body`; page items and banners include it.
+Nullable fields remain serialized as JSON null.
+
+## Next handoff: additive payload fields
+
+- Summary adds `inboxHref`, currently `/app/notifications`. Retain and honor the existing
+ `notificationsHref` and `alertsHref` fields for older clients.
+- Every inbox item (including banners) adds `actions`: `name`, `label`, `operationId`.
+ An unread item offers `read` (`alertsInboxRead`); a read item offers `unread`
+ (`alertsInboxUnread`); both offer `dismiss` (`alertsInboxDismiss`). Invoke the operation
+ from the fragment with `{ "id": item.id }`. Existing item `href` semantics are unchanged.
+- Every record-panel alert adds `actions`: `name`, `label`, `processName`, `href`.
+ Managers see `acknowledge` for OPEN alerts and `snooze` for panel alerts; viewers get an
+ empty list. The href is complete, including the process, alert id and table arguments:
+ `/app/alertsAcknowledge?recordsParam=recordIds&recordIds=9&tableName=alerts_alert`.
+ Clients follow the provided href without constructing a process or table name. Server
+ process authorization still applies when an action executes.
+- Panel adds `follow: { "available": boolean }`, true exactly when core supplemental
+ `MiddlewareSpecExtensionsMetaData` contains an extension named `collaboration`. No
+ collaboration dependency or URL is embedded here. Next uses its collaboration module
+ to determine the follow endpoint and current follow state.
+
+The provided Next `main` checkout had no alerts client at implementation time. The reviewed
+client was `feature/notification-center:src/lib/api/alerts.ts` and its
+`src/components/alerts/*` components. Its current process href and watch assumptions are
+covered by the migration above.
+
+## Removed watch operation
+
+`POST /qqq/v1/alerts/record-panel/watch` and `AlertsApi.watchRecord` are removed. No panel
+request creates a RECORD subscription. `alerts_subscription` continues to handle alert-type
+and scope subscriptions; existing record-scoped watch rows are deprecated and remain readable
+(and retain their existing routing behavior). No rows are migrated or deleted. The panel's
+`watching` boolean is retained for compatibility and reports only those legacy rows; it must
+not be used as collaboration follow state.
+
+There is no follower fan-out yet. `AlertRouter.destinations` contains
+`STAND-IN(core=QRun-IO/qqq#1164, removeBy=4.1.0-RC.7)` at the point where follower destinations
+will join the existing recipient visibility and suppression pipeline.
+
+## Alertmanager machine webhook
+
+Canonical operation: **POST `/qqq/hooks/alerts/alertmanager`**. This is a raw machine route
+outside the UI API and has no UI operationId. It preserves the bearer token, secret reference,
+JSON request and `{raised, resolved, ignored}` response. No QQQ user session is required.
+
+The previous **POST `/qqq/v1/alerts/ingest/alertmanager`** is an opt-in raw alias:
+
+```java
+new AlertsQBitConfig()
+ .withLegacyAlertmanagerPath(true)
+ .withAlertmanagerIngestTokenSecretRef("ALERTMANAGER_TOKEN");
+```
+
+`legacyAlertmanagerPath` defaults to false. Core `UiApiRouteAuditor` compares every raw v1
+route against specs and offers no machine-webhook exemption. Therefore enabling the alias
+requires non-strict UI route mode; strict startup rejects it. It is intentionally absent
+from the UI fragment. Default hosts and all application-server tests run strict successfully.
+The migration-only raw provider is tested separately, including strict-auditor rejection.
+
+Both paths call the same handler and authenticate against the same secret. Every alias call,
+including rejected authentication, logs a warning and carries:
+
+```http
+Deprecation: true
+Sunset: Tue, 13 Apr 2027 00:00:00 GMT
+```
+
+The alias is supported for the 0.1 minor line only and scheduled for removal in 0.2; the
+published calendar sunset is 13 April 2027. Move receivers before either retirement boundary.
+No automatic runtime cutoff is applied by these informational headers.
+
+## Verification
+
+Use the Maven wrapper. The committed BOM version is RC.6; while it is unreleased, verify with
+an already installed local core build:
+
+```sh
+./mvnw -B verify -Dqqq.snapshot.version=4.1.0-ARCHRC6-SNAPSHOT
+```
+
+Regenerate intentionally with `-Dtest=AlertsApiContractTest -Dalerts.openapi.update=true test`
+(and the same local version override). Normal tests compare the fragment without rewriting it.
+Checkstyle uses `checkstyle/config.xml` and the repository's existing short Apache headers.
diff --git a/pom.xml b/pom.xml
index 881be06..7572037 100644
--- a/pom.xml
+++ b/pom.xml
@@ -107,6 +107,32 @@
+
+ org.apache.maven.plugins
+ maven-checkstyle-plugin
+ 3.6.0
+
+
+ com.puppycrawl.tools
+ checkstyle
+ 14.3.0
+
+
+
+ checkstyle/config.xml
+ true
+ warning
+
+
+
+ checkstyle
+ validate
+
+ check
+
+
+
+
org.jacoco
jacoco-maven-plugin
@@ -158,14 +184,14 @@
-
+
qqq-snapshot
true
- 4.1.0-RC.5
+ 4.1.0-RC.6
diff --git a/src/main/java/com/kingsrook/qbits/alerts/AlertsQBitConfig.java b/src/main/java/com/kingsrook/qbits/alerts/AlertsQBitConfig.java
index bebe252..cf78b56 100644
--- a/src/main/java/com/kingsrook/qbits/alerts/AlertsQBitConfig.java
+++ b/src/main/java/com/kingsrook/qbits/alerts/AlertsQBitConfig.java
@@ -74,6 +74,8 @@ public class AlertsQBitConfig implements QBitConfig
public static final String APP_NAME = "alerts";
+ private Boolean legacyAlertmanagerPath = false;
+
private String backendName;
private String schedulerName;
private String messagingProviderName;
@@ -610,7 +612,7 @@ public class AlertsQBitConfig implements QBitConfig
/*******************************************************************************
** Fluent setter for alertmanagerIngestTokenSecretRef: the reference (an
** environment variable name or ${...} expression, never the value) of the
- ** bearer token Alertmanager sends to POST /qqq/v1/alerts/ingest/alertmanager.
+ ** bearer token Alertmanager sends to POST /qqq/hooks/alerts/alertmanager.
** Null (the default) turns ingest off.
*******************************************************************************/
public AlertsQBitConfig withAlertmanagerIngestTokenSecretRef(String alertmanagerIngestTokenSecretRef)
@@ -793,4 +795,26 @@ public class AlertsQBitConfig implements QBitConfig
this.jobRunLinkPattern = jobRunLinkPattern;
return (this);
}
+
+
+
+ /*******************************************************************************
+ ** Whether to expose the deprecated UI-path webhook alias (default false).
+ ** Requires non-strict UI route mode; remove after one minor release.
+ *******************************************************************************/
+ public Boolean getLegacyAlertmanagerPath()
+ {
+ return (this.legacyAlertmanagerPath);
+ }
+
+
+
+ /*******************************************************************************
+ ** Opt in to the deprecated alias while migrating Alertmanager receivers.
+ *******************************************************************************/
+ public AlertsQBitConfig withLegacyAlertmanagerPath(Boolean legacyAlertmanagerPath)
+ {
+ this.legacyAlertmanagerPath = legacyAlertmanagerPath;
+ return (this);
+ }
}
diff --git a/src/main/java/com/kingsrook/qbits/alerts/AlertsQBitProducer.java b/src/main/java/com/kingsrook/qbits/alerts/AlertsQBitProducer.java
index 2e43e61..83f8686 100644
--- a/src/main/java/com/kingsrook/qbits/alerts/AlertsQBitProducer.java
+++ b/src/main/java/com/kingsrook/qbits/alerts/AlertsQBitProducer.java
@@ -212,6 +212,7 @@ public class AlertsQBitProducer implements QBitMetaDataProducer> actions = new ArrayList<>();
+ actions.add(Map.of("name", entry.unread() ? "read" : "unread", "label", entry.unread() ? "Mark read" : "Mark unread",
+ "operationId", entry.unread() ? InboxReadSpec.OPERATION_ID : InboxUnreadSpec.OPERATION_ID));
+ actions.add(Map.of("name", "dismiss", "label", "Dismiss", "operationId", InboxDismissSpec.OPERATION_ID));
+ row.put("actions", actions);
return (row);
}
@@ -582,46 +586,13 @@ public class AlertsApi
result.put("viewAllHref", allIds.isEmpty() ? null : AlertsHome.listUrl(new QQueryFilter().withCriteria("id", QCriteriaOperator.IN, dedupe(allIds))));
result.put("canManage", session.hasPermission(AlertsQBitConfig.PERMISSION_MANAGE));
result.put("watching", isWatching(tableName, recordId));
+ MiddlewareSpecExtensionsMetaData extensions = MiddlewareSpecExtensionsMetaData.of(runtime.getQInstance());
+ result.put("follow", Map.of("available", extensions != null && extensions.getExtensions().containsKey("collaboration")));
return (result);
}
- /*******************************************************************************
- ** "Watch alerts for this record": a RECORD-scope subscription for the user.
- *******************************************************************************/
- public Map watchRecord(Map body) throws QException
- {
- String userId = requireViewer();
- String tableName = body == null ? null : (String) body.get("tableName");
- Object recordId = body == null ? null : body.get("recordId");
- if(userId == null || !StringUtils.hasContent(tableName) || recordId == null || runtime.getQInstance().getTable(tableName) == null)
- {
- throw (new QBadRequestException("Send a table name and record id."));
- }
- if(isWatching(tableName, String.valueOf(recordId)))
- {
- return (Map.of("watching", true, "created", false));
- }
- List inserted = InsertAction.executeForRecords(new InsertInput(AlertsSubscription.TABLE_NAME).withRecord(new QRecord()
- .withValue("subscriberType", SubscriberType.USER.name())
- .withValue("userId", userId)
- .withValue("scopeType", ScopeType.RECORD.name())
- .withValue("scopeValue", tableName)
- .withValue("scopeRecordId", String.valueOf(recordId))
- .withValue("minSeverity", AlertSeverity.WARNING.name())
- .withValue("events", "RAISED,ESCALATED,REMINDER,RESOLVED")
- .withValue("deliveryMode", "IMMEDIATE")
- .withValue("enabled", true)));
- if(CollectionUtils.nullSafeHasContents(inserted.get(0).getErrors()))
- {
- throw (new QBadRequestException(inserted.get(0).getErrors().get(0).getMessage()));
- }
- return (Map.of("watching", true, "created", true, "subscriptionId", inserted.get(0).getValue("id")));
- }
-
-
-
/*******************************************************************************
** Whether the user already has a RECORD subscription for this record.
*******************************************************************************/
@@ -654,11 +625,32 @@ public class AlertsApi
row.put("age", AlertMessageRenderer.age(alert.getValueInstant("firstRaisedAt"), now));
row.put("raisedAt", alert.getValueInstant("firstRaisedAt") == null ? null : alert.getValueInstant("firstRaisedAt").toString());
row.put("href", "/app/" + AlertsAlert.TABLE_NAME + "/" + alert.getValue("id"));
+ List