Address review feedback on the batched RPC persistence PR:
- JpaRpcDao: replace field @Autowired with @RequiredArgsConstructor + final
fields, matching the sibling batch DAOs (JpaBaseEventDao, JpaBaseEdgeEventDao).
- TbRpcService: route the update path's post-persist notification through
notifyRuleEngine() so create and update share identical try/catch + logging.
The initial RPC create is now persisted synchronously before sending
(persist-before-send); only status updates remain on the async batched
queue. The batched-insert plumbing that was retained for reversibility is
now dead code and is removed:
- remove createAsync from RpcService/RpcDao/BaseRpcService/JpaRpcDao
- delete RpcQueueEntry (insert/update discriminator no longer needed)
- rename RpcInsertRepository -> RpcUpdateRepository; the queue now carries
RpcEntity directly and batches UPDATEs only
- re-aim JpaRpcDaoTest / TbRpcServiceTest off the removed insert API
No behavior change: update batching, uuid partitioning/sort, and the
response COALESCE preservation are all unchanged.
The device-install wizard substituted ${...} placeholders via a plain
string replace, so a quoted boolean macro like "cleanSession":
"${mqttCleanSession}" parsed as the string "false" (truthy) instead of
the JSON boolean false. Unchecking a boolean field (e.g. clean session)
therefore had no effect and read as true in the resulting integration.
Add a type-aware resolveTemplateJson() that, using the form-field type
recorded in form.json, emits BOOLEAN/INTEGER placeholders occupying a
whole quoted token as raw JSON values (unquoted) while leaving string
fields and embedded placeholders quoted. Route all template-parse call
sites through it. Fixes cleanSession, ssl, port and connectTimeoutSec
across all existing device packages without any template changes.
The previous version stubbed both writes with immediateFuture and submitted them sequentially, so the QUEUED-before-DELIVERED order held by construction and would have passed even if per-rpcId striping regressed to a shared multi-threaded pool. Make the QUEUED notification block (signal start via latch, then sleep) and submit DELIVERED only once QUEUED is in flight, so the assertion now depends on the single-thread stripe serializing same-rpcId callbacks.
On the update paths buildRpc rebuilt the Rpc with createdTime=now, which the post-persist rule-engine notification then serialized, so RPC_DELIVERED/etc. carried the update moment instead of the row's real creation time (the UPDATE never touches created_time). Capture createdTime once at create and thread it through ToDeviceRpcRequestMetadata so update-path notifications report the original value, restoring pre-async behavior.
Also add a striped-executor ordering test (same rpcId -> RPC_QUEUED before RPC_DELIVERED) and document that sql.rpc.callback_threads is independent of batch_threads.
- build saveOrUpdate result inside the transaction so updateCounts is a
non-null local taken straight from batchUpdate, avoiding an NPE on the
@Nullable transactionTemplate.execute() result
- guard HashPartitioner against a non-positive partition count
- fail fast in TbRpcService when sql.rpc.callback_threads < 1
- add a deterministic saveOrUpdate coalesced-batch test and rename the
timing-dependent async test to reflect what it actually verifies
- Rename TbRpcService.save -> update so the create/update pairing is
self-documenting (matches createAsync/updateAsync); update all actor
call sites and unit tests.
- Extract the (hash & 0x7FFFFFFF) % n striping into a shared
HashPartitioner helper used by both TbSqlBlockingQueueWrapper and
TbRpcService, so the end-to-end submission-order invariant has a single
source of truth.
- Collapse the insert/update split in RpcInsertRepository.saveOrUpdate
into a single partitioningBy pass.
- Remove the unused TbRpcService.findRpcById (and its now-orphaned import).
- Restore the prime-thread-count guidance on the RPC batch_threads /
callback_threads yml comments.
Async RPC persistence now returns a per-write Boolean: an INSERT-on-conflict always persists (true), while an UPDATE-by-id reports false when its WHERE id = ? matched no row (RPC deleted via TTL/manual delete). TbRpcService notifies the rule engine only when the write actually persisted, restoring the findById-null skip the async refactor had dropped.
Also: drop the unused tenantId param from createAsync/updateAsync; make TbRpcService's callback-thread count constructor-injectable (removes test reflection) with a comment on why callback striping exists alongside the queue partitioning; correct the RPC batch_threads yml comment; assert the Boolean contract in JpaRpcDaoTest and add a notification-suppression unit test.
Split the batched RPC persistence into a create path (INSERT ... ON CONFLICT)
and an update path (UPDATE ... WHERE id = ?). A status update for a row deleted
in the meantime (TTL cleanup / manual delete) is no longer resurrected,
restoring the old findById-null skip. Both paths COALESCE the response so a
status update that carries none no longer clobbers a previously stored one.
The create-vs-update intent is carried by a dedicated RpcQueueEntry record
(keeping RpcEntity pure data) and exposed via explicit createAsync/updateAsync
DAO methods instead of a boolean flag; insert/update batch scaffolding is
collapsed into a shared helper.
Adds DAO regression tests (re-queue keeps existing row, null-response
preservation, no resurrection of a deleted row) and TbRpcService create/update
wiring tests.
* feat: send pseudonymized install report (tenant/user hash, tb version) to IoT Hub
* refactor: source install-report tb version/edition from ProjectInfo
Use the existing ProjectInfo component (getProjectVersion/getProductType)
instead of an ad-hoc optional BuildProperties field and a hard-coded "CE"
edition, so PE reports its own product type and the version is the canonical
cleaned value. Constructor-injected via @RequiredArgsConstructor.
Push the deleteInstalledItem call from IotHubActionsService into the dialog's confirm() handler so the dialog itself manages its lifetime. Pass installedItemId in the dialog data and have confirm() fire the API call, then close with true on success. Drop the now-unused isLoading flips in the installed-items table since loading state is no longer optimistically toggled around the dialog.
Drop ignoreErrors:true on the two saveDashboard calls in the device-install dialog so backend failures (validation errors, missing permissions, entity-limit hits) surface to the global interceptor instead of being swallowed and reported as a generic "install failed" with no detail.
PROD-8423: .tb-main-content (the scroll container holding the router-outlet in home.component.html) keeps its scrollTop across route changes, so navigating from a scrolled-down IoT Hub home to a child page (e.g. Calculated Fields) opened the new page already scrolled. Angular's scrollPositionRestoration only handles window scroll, not arbitrary containers. Add a #mainContent ref, capture the ElementRef, and reset its scroll position in the existing (activate) handler so every route navigation lands at the top.
PROD-8420: .tb-iot-hub-sort-row was locked to height: 32px, so when many use-case filters were selected the chip-set wrapped to multiple lines but the row stayed one line tall and the card grid below rendered on top of the wrapped chips. Switch to min-height: 32px so the row keeps its baseline single-line height when empty but grows naturally with wrapped chips.
IotHubActionsService.deleteItem now returns true/false (no cancel-as-EMPTY) and updateItem returns string|boolean; callers no longer early-return on a null installed item and instead let the service produce a false, so the subscriber consistently fires. Installed-items table shows a translucent loading overlay during update/delete via isLoading + relative-positioned container. Replace mr-2 inline-block align-middle hacks on dialog button spinners with matButtonIcon, and use matButtonIcon for the check-for-updates button icon/spinner too.
Detail dialog non-CF/RC layout stacks the preview and description on lt-md instead of overflowing horizontally. Hero cluster moves up to 15% and title drops to 28px on the smallest viewport. getInstalledItemUrl for DEVICE descriptors now falls back to the first DEVICE entry in createdEntityIds when dashboardId is missing, so single-device packages still resolve to an entity URL.