- Introduce HasAppliedToEntity interface used by CreatedAlarmRuleInfo and
CreatedCalculatedFieldInfo to share the entity page-link logic and
cover DEVICE / ASSET in addition to the profile types.
- Add static from(EntityId, name, CalculatedField) factories to both
records; SolutionInstallContext now uses them and detects ALARM
calculated fields via type instead of the previous TODO/hardcoded
false.
- AlarmSeverity and CalculatedFieldType expose display names used when
formatting created-alarm-rule severities and CF type column.
- DefaultSolutionService switches the CF arguments check from
BaseCalculatedFieldConfiguration to ArgumentsBasedCalculatedFieldConfiguration
and throws ThingsboardRuntimeException for missing references.
Re-throw EntitiesLimitExceededException from IoT Hub item install and
solution-template install paths instead of folding it into the generic
install error response, so the PE entity-limit error reaches the global
HTTP error handler.
In the device install wizard switch saveDevice/createDevice (and
overwrite) calls from ignoreErrors:true to false so the limit-exceeded
toast is shown when the backend rejects the create.
The guard around iterating createdEntityIds was checking isEmpty()
instead of !isEmpty(), so entity deletion was skipped whenever there
was actually data to clean up.
SolutionTemplateInstalledItemDescriptor and SolutionInstallResponse now
carry tenantTelemetryKeys and tenantAttributeKeys lists. SolutionService
.deleteSolution takes the full descriptor (instead of just the
created-entity list) so uninstall can also clean up tenant-scoped
telemetry/attributes. Update IotHub install descriptor population and
delete call sites; force-update path logs and continues if delete throws.
Spring Boot 3.5 deprecated RestTemplateBuilder.setConnectTimeout(Duration) and
setReadTimeout(Duration) for removal; their non-deprecated replacements
connectTimeout(Duration) and readTimeout(Duration) are direct, behavior-equivalent
delegations.
Propagate User through SolutionInstallContext, DefaultSolutionService,
and TbCalculatedFieldService.delete so non-security-context callers can
drive solution installs and calculated-field cleanup. Pass
TenantSolutionTemplateInstructions into the context constructor so the
caller controls instruction state.
Add CreatedRuleChainInfo extending CreatedEntityInfo that overrides
getEntityPageLink to produce /edgeManagement/ruleChains/<id> for EDGE
rule chains and /ruleChains/<id> otherwise. SolutionInstallContext now
registers rule chains with this type-aware info.
Creates missing system images from application/src/main/data/resources/images
during LTS patch startup, mirroring the upgrade-path loadSystemResources logic.
Existing system images in the DB are left untouched.
Several testSaveProtoDeviceProfileWithInvalidRpcRequestSchema* tests
intermittently fail with:
org.thingsboard.server.dao.exception.TenantNotFoundException: Tenant
with id <fresh-tenant-uuid> not found
when the tenant created in @Before has not yet been populated in the
tenant profile cache by the time the request hits the partition-lookup
path (DefaultTenantRoutingInfoService -> TbTenantProfileCache ->
TenantService#findTenantById). The underlying request is idempotent
(the schema is invalid so it is rejected with 400 regardless of
retries), so wrap the doPost + status assertion in Awaitility with
Mockito.reset inside the retry block: only the last attempt's
invocations are visible to the subsequent verify* assertions.
Applies to all testSaveDeviceProfileWithInvalidRpcRequestProtoSchema
callers, including the currently-muted
testSaveProtoDeviceProfileWithInvalidRpcRequestSchemaRequestIdDateType.
The test asserts exactly 2 UserCredentialsUpdateMsg after creating a new
tenant-admin user, but the user activation flow can emit either 2 or 3
depending on timing:
- activateUserCredentials publishes CREDENTIALS_UPDATED (msg #1)
- setUserCredentialsEnabled publishes CREDENTIALS_UPDATED (msg #2)
- the initial USER ADDED edge event is processed asynchronously in
UserEdgeProcessor and bundles an extra UserCredentialsUpdateMsg when
it finds userCredentials.isEnabled() == true (i.e. activation
already raced past the ADDED event)
When the race goes the second way we end up with 1 UserUpdateMsg plus
3 UserCredentialsUpdateMsg, which currently fails the hard-coded
assertEquals(2, ...) assertion.
Accept both 2 and 3 UserCredentialsUpdateMsg instead of asserting an
exact count, matching the reality of the asynchronous edge event
pipeline.
Await cached resource data to become available after save eviction
before asserting, and await null after deletion. Prevents Mockito
verifyNoMoreInteractions(resourceService) failure caused by racing
background cache-load invocations.
Backport of 99334ba7fe from master.
- Mark force-added entity-field filter mappings as ignored under OR so
EntityDataAdapter keeps the response shape identical to AND (applied
to both findEntityDataByQuery and validateEntityCountQuery paths).
- Skip the ON-clause filter in toLatestJoin when forceLeftJoin=true to
avoid duplicating the predicate that buildQuery already emits at the
middle layer under OR (different bound parameter names, same filter).
- Document the empty-predicate drop in buildQuery (safe under AND,
narrowing under OR) and the defensive vacuously-true return in
RepositoryUtils.checkKeyFilters.
- Explain why validateEntityCountQuery uses the nullable
getKeyFiltersOperation() rather than the OrDefault helper.
- Add regression test asserting latest[ENTITY_FIELD] under OR only
contains keys declared in entityFields.
- New TbIotHubInstalledItemsDialogComponent opened from detail dialog Manage section
- Extract TbIotHubInstalledItemsTableComponent with itemVersion mode for single-item view
- Add installed item counts API (GET /installedItems/counts) and itemId filter to installed items list
- Wire installed counts into home, browse, search, and detail dialog (replaces installedDevices list)
- Propagate ChipOverflowDirective with minChips input; use on detail dialog meta chips
- openEntity opens in new tab; "View item details" swaps to "Review device instructions" for DEVICE items
- EntityKeyMapping: replace fragile null-swap of entityKeyColumn with a useAliasAsField flag threaded through buildKeyQuery/buildPredicateQuery, avoiding mutation of shared instance state
- TbAlarmCountSubCtx, TbAlarmDataSubCtx: normalize keyFiltersOperation at query construction via getKeyFiltersOperationOrDefault(), matching DefaultEntityQueryService
- BaseEntityService: end the disabled-OR validation error with a period and attribute it to the system administrator, aligning with the existing UI translation
- EntityQueryControllerTest: extract helpers (createDeviceWithSharedAttributes, createDeviceWithTimeseries, createAlarm, deviceTypeFilter, numericKeyFilter, stringKeyFilter, pageLinkSortedByName, nameEntityField, extractNames) and replace the pagination test's obscure temperature ternary with an explicit array
Redesign item detail dialog: move install/add/update/open buttons to footer,
move creator to sticky meta bar with avatar and verified badge, add manage
section with solution instructions, remove, and installed items count buttons,
update subtitle with grouped icon+text pairs matching design.
Add getInstalledItemCounts API (backend + frontend) returning item counts
by itemId per type. Replace installedDevices list with count-based tracking
in home, browse, and search components. Add installedItemsCount to item card
and detail dialog data flow.
Move fetchArguments() and state.update() before state.init() in initState() so that arguments are available when init() triggers reeval for DURATION conditions with active tracking (firstEventTs > 0).
Relax version check to allow maintenance digit increases within the same
LTS family (e.g. 4.3.0 -> 4.3.1), not just patch digit increases.
Add LTS SQL schema patch execution from upgrade/lts/schema_update.sql,
running before views and widget updates so schema changes are in place
for dependent objects.
The /api/ai/model/chat endpoint returns DeferredResult (async), so
MockMvc needs asyncDispatch() to capture the resolved response body.
Without it the test sees an empty body and fails on deserialization.
The runtime SSRF test called /api/ai/chat, but the actual endpoint is
/api/ai/model/chat (mapped via @RequestMapping("/api/ai/model") +
@PostMapping("/chat")).
Wrap configure() call in AiChatModelServiceImpl.sendChatRequestAsync
so a synchronous exception (e.g. runtime SSRF block) is converted to
a failed future and caught by the controller's .catching() chain,
returning TbChatResponse.Failure instead of a raw 500.
Apply SsrfProtectionValidator.validateUri() to AI providers with
user-supplied URLs (OpenAI baseUrl, Azure OpenAI endpoint, Ollama
baseUrl). Validation at two layers:
- execution time in Langchain4jChatModelConfigurerImpl
- save time in AiModelDataValidator
Controlled by the existing SSRF_PROTECTION_ENABLED flag.
Replace the blocking semaphore guard with a non-blocking bounded FIFO queue
+ semaphore pattern:
- No semaphore/queue when maxParallelRequestsCount=0 (default): direct doHttpCall,
identical to the old behavior.
- When a concurrency limit is set, incoming messages are enqueued via non-blocking
offer(); a full queue triggers onFailure immediately.
- tryProcess() acquires one semaphore slot and dispatches the next valid queued task.
Stale tasks (batch deadline expired) are dropped and the slot reused in the same pass.
- doFinally hook releases the semaphore and calls tryProcess() exactly once after any
terminal signal (success, error, cancel), preventing double-release and permit leaks.
- publishOn(externalCallExecutor) moves callbacks off reactor-netty I/O threads.
System-level safety caps are wired through thingsboard.yml → ActorSystemContext →
TbContext → TbHttpClient, scoped to rule-engine services only via @TbRuleEngineComponent:
actors.rule.external.http_client.max_parallel_requests (ACTORS_RULE_EXTERNAL_HTTP_CLIENT_MAX_PARALLEL_REQUESTS)
actors.rule.external.http_client.max_pending_requests (ACTORS_RULE_EXTERNAL_HTTP_CLIENT_MAX_PENDING_REQUESTS)
actors.rule.external.http_client.pool_max_connections (ACTORS_RULE_EXTERNAL_HTTP_CLIENT_POOL_MAX_CONNECTIONS)
Backward compat: TB_RE_HTTP_CLIENT_POOL_MAX_CONNECTIONS still honored via yaml fallback.
Observability: five AtomicLong counters (dispatched, success, failure, droppedQueueFull,
droppedStale) with periodic WARN anomaly logging including semaphorePermits for leak detection.
No configuration changes or upgrade scripts required — docker image update is sufficient.
Rename RestApiCallNodeSettings to TbHttpClientSettings
The settings are about HTTP client transport concerns (connection pool,
concurrency, queue depth), not REST API Call node business logic.
The new name matches the consumer (TbHttpClient) and the YAML path
(actors.rule.external.http_client.*).