Merge branch 'rc' of github.com:thingsboard/thingsboard into cf-fixes

This commit is contained in:
IrynaMatveieva 2025-03-19 15:08:33 +02:00
commit 5ee6d41f77
23 changed files with 147 additions and 76 deletions

View File

@ -30,13 +30,12 @@ import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldStateProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueCallback;
import org.thingsboard.server.queue.TbQueueMsgHeaders; import org.thingsboard.server.queue.TbQueueMsgHeaders;
import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.TbQueueMsgMetadata;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.state.KafkaQueueStateService;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager; import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
import org.thingsboard.server.queue.common.state.KafkaQueueStateService;
import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.PartitionService;
import org.thingsboard.server.queue.discovery.QueueKey; import org.thingsboard.server.queue.discovery.QueueKey;
import org.thingsboard.server.queue.kafka.TbKafkaProducerTemplate; import org.thingsboard.server.queue.kafka.TbKafkaProducerTemplate;
@ -59,7 +58,6 @@ public class KafkaCalculatedFieldStateService extends AbstractCalculatedFieldSta
private final TbRuleEngineQueueFactory queueFactory; private final TbRuleEngineQueueFactory queueFactory;
private final PartitionService partitionService; private final PartitionService partitionService;
private final TbQueueAdmin queueAdmin;
@Value("${queue.calculated_fields.poll_interval:25}") @Value("${queue.calculated_fields.poll_interval:25}")
private long pollInterval; private long pollInterval;
@ -94,7 +92,7 @@ public class KafkaCalculatedFieldStateService extends AbstractCalculatedFieldSta
} }
}) })
.consumerCreator((config, partitionId) -> queueFactory.createCalculatedFieldStateConsumer()) .consumerCreator((config, partitionId) -> queueFactory.createCalculatedFieldStateConsumer())
.queueAdmin(queueAdmin) .queueAdmin(queueFactory.getCalculatedFieldQueueAdmin())
.consumerExecutor(eventConsumer.getConsumerExecutor()) .consumerExecutor(eventConsumer.getConsumerExecutor())
.scheduler(eventConsumer.getScheduler()) .scheduler(eventConsumer.getScheduler())
.taskExecutor(eventConsumer.getTaskExecutor()) .taskExecutor(eventConsumer.getTaskExecutor())

View File

@ -42,7 +42,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldLinke
import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.CalculatedFieldTelemetryMsgProto;
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCalculatedFieldNotificationMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager; import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
@ -81,7 +80,6 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
private long packProcessingTimeout; private long packProcessingTimeout;
private final TbRuleEngineQueueFactory queueFactory; private final TbRuleEngineQueueFactory queueFactory;
private final TbQueueAdmin queueAdmin;
private final CalculatedFieldStateService stateService; private final CalculatedFieldStateService stateService;
public DefaultTbCalculatedFieldConsumerService(TbRuleEngineQueueFactory tbQueueFactory, public DefaultTbCalculatedFieldConsumerService(TbRuleEngineQueueFactory tbQueueFactory,
@ -94,12 +92,10 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
ApplicationEventPublisher eventPublisher, ApplicationEventPublisher eventPublisher,
JwtSettingsService jwtSettingsService, JwtSettingsService jwtSettingsService,
CalculatedFieldCache calculatedFieldCache, CalculatedFieldCache calculatedFieldCache,
TbQueueAdmin queueAdmin,
CalculatedFieldStateService stateService) { CalculatedFieldStateService stateService) {
super(actorContext, tenantProfileCache, deviceProfileCache, assetProfileCache, calculatedFieldCache, apiUsageStateService, partitionService, super(actorContext, tenantProfileCache, deviceProfileCache, assetProfileCache, calculatedFieldCache, apiUsageStateService, partitionService,
eventPublisher, jwtSettingsService); eventPublisher, jwtSettingsService);
this.queueFactory = tbQueueFactory; this.queueFactory = tbQueueFactory;
this.queueAdmin = queueAdmin;
this.stateService = stateService; this.stateService = stateService;
} }
@ -114,7 +110,7 @@ public class DefaultTbCalculatedFieldConsumerService extends AbstractConsumerSer
.pollInterval(pollInterval) .pollInterval(pollInterval)
.msgPackProcessor(this::processMsgs) .msgPackProcessor(this::processMsgs)
.consumerCreator((config, partitionId) -> queueFactory.createToCalculatedFieldMsgConsumer()) .consumerCreator((config, partitionId) -> queueFactory.createToCalculatedFieldMsgConsumer())
.queueAdmin(queueAdmin) .queueAdmin(queueFactory.getCalculatedFieldQueueAdmin())
.consumerExecutor(consumersExecutor) .consumerExecutor(consumersExecutor)
.scheduler(scheduler) .scheduler(scheduler)
.taskExecutor(mgmtExecutor) .taskExecutor(mgmtExecutor)

View File

@ -121,14 +121,14 @@ public class AssetProfileEdgeTest extends AbstractEdgeTest {
Assert.assertNull(assetProfile.getDefaultRuleChainId()); Assert.assertNull(assetProfile.getDefaultRuleChainId());
Assert.assertEquals(edgeRuleChainId, assetProfile.getDefaultEdgeRuleChainId()); Assert.assertEquals(edgeRuleChainId, assetProfile.getDefaultEdgeRuleChainId());
// delete profile // delete profile and delete relation messages
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(2);
doDelete("/api/assetProfile/" + assetProfile.getUuidId()) doDelete("/api/assetProfile/" + assetProfile.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Optional<AssetProfileUpdateMsg> assetDeleteMsgOpt = edgeImitator.findMessageByType(AssetProfileUpdateMsg.class);
Assert.assertTrue(latestMessage instanceof AssetProfileUpdateMsg); Assert.assertTrue(assetDeleteMsgOpt.isPresent());
AssetProfileUpdateMsg assetProfileUpdateMsg = (AssetProfileUpdateMsg) latestMessage; AssetProfileUpdateMsg assetProfileUpdateMsg = assetDeleteMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType());
Assert.assertEquals(assetProfile.getUuidId().getMostSignificantBits(), assetProfileUpdateMsg.getIdMSB()); Assert.assertEquals(assetProfile.getUuidId().getMostSignificantBits(), assetProfileUpdateMsg.getIdMSB());
Assert.assertEquals(assetProfile.getUuidId().getLeastSignificantBits(), assetProfileUpdateMsg.getIdLSB()); Assert.assertEquals(assetProfile.getUuidId().getLeastSignificantBits(), assetProfileUpdateMsg.getIdLSB());

View File

@ -327,14 +327,14 @@ public class DeviceProfileEdgeTest extends AbstractEdgeTest {
Assert.assertNotNull(deviceProfile); Assert.assertNotNull(deviceProfile);
Assert.assertEquals("Device Profile On Edge", deviceProfile.getName()); Assert.assertEquals("Device Profile On Edge", deviceProfile.getName());
// delete profile // delete profile and delete relation messages
edgeImitator.expectMessageAmount(1); edgeImitator.expectMessageAmount(2);
doDelete("/api/deviceProfile/" + deviceProfile.getUuidId()) doDelete("/api/deviceProfile/" + deviceProfile.getUuidId())
.andExpect(status().isOk()); .andExpect(status().isOk());
Assert.assertTrue(edgeImitator.waitForMessages()); Assert.assertTrue(edgeImitator.waitForMessages());
AbstractMessage latestMessage = edgeImitator.getLatestMessage(); Optional<DeviceProfileUpdateMsg> deviceDeleteMsgOpt = edgeImitator.findMessageByType(DeviceProfileUpdateMsg.class);
Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); Assert.assertTrue(deviceDeleteMsgOpt.isPresent());
DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceDeleteMsgOpt.get();
Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType());
Assert.assertEquals(deviceProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB()); Assert.assertEquals(deviceProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB());
Assert.assertEquals(deviceProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB()); Assert.assertEquals(deviceProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB());

View File

@ -53,7 +53,6 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.EdqsEventMsg; import org.thingsboard.server.gen.transport.TransportProtos.EdqsEventMsg;
import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueHandler; import org.thingsboard.server.queue.TbQueueHandler;
import org.thingsboard.server.queue.TbQueueResponseTemplate; import org.thingsboard.server.queue.TbQueueResponseTemplate;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
@ -92,7 +91,6 @@ public class EdqsProcessor implements TbQueueHandler<TbProtoQueueMsg<ToEdqsMsg>,
private final EdqsPartitionService partitionService; private final EdqsPartitionService partitionService;
private final ConfigurableApplicationContext applicationContext; private final ConfigurableApplicationContext applicationContext;
private final EdqsStateService stateService; private final EdqsStateService stateService;
private final TbQueueAdmin queueAdmin;
private PartitionedQueueConsumerManager<TbProtoQueueMsg<ToEdqsMsg>> eventConsumer; private PartitionedQueueConsumerManager<TbProtoQueueMsg<ToEdqsMsg>> eventConsumer;
private TbQueueResponseTemplate<TbProtoQueueMsg<ToEdqsMsg>, TbProtoQueueMsg<FromEdqsMsg>> responseTemplate; private TbQueueResponseTemplate<TbProtoQueueMsg<ToEdqsMsg>, TbProtoQueueMsg<FromEdqsMsg>> responseTemplate;
@ -143,7 +141,7 @@ public class EdqsProcessor implements TbQueueHandler<TbProtoQueueMsg<ToEdqsMsg>,
consumer.commit(); consumer.commit();
}) })
.consumerCreator((config, partitionId) -> queueFactory.createEdqsMsgConsumer(EdqsQueue.EVENTS)) .consumerCreator((config, partitionId) -> queueFactory.createEdqsMsgConsumer(EdqsQueue.EVENTS))
.queueAdmin(queueAdmin) .queueAdmin(queueFactory.getEdqsQueueAdmin())
.consumerExecutor(consumersExecutor) .consumerExecutor(consumersExecutor)
.taskExecutor(taskExecutor) .taskExecutor(taskExecutor)
.scheduler(scheduler) .scheduler(scheduler)

View File

@ -30,7 +30,6 @@ import org.thingsboard.server.edqs.processor.EdqsProducer;
import org.thingsboard.server.edqs.util.VersionsStore; import org.thingsboard.server.edqs.util.VersionsStore;
import org.thingsboard.server.gen.transport.TransportProtos.EdqsEventMsg; import org.thingsboard.server.gen.transport.TransportProtos.EdqsEventMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager; import org.thingsboard.server.queue.common.consumer.PartitionedQueueConsumerManager;
import org.thingsboard.server.queue.common.consumer.QueueConsumerManager; import org.thingsboard.server.queue.common.consumer.QueueConsumerManager;
@ -56,7 +55,6 @@ public class KafkaEdqsStateService implements EdqsStateService {
private final EdqsConfig config; private final EdqsConfig config;
private final EdqsPartitionService partitionService; private final EdqsPartitionService partitionService;
private final EdqsQueueFactory queueFactory; private final EdqsQueueFactory queueFactory;
private final TbQueueAdmin queueAdmin;
private final TopicService topicService; private final TopicService topicService;
@Autowired @Lazy @Autowired @Lazy
private EdqsProcessor edqsProcessor; private EdqsProcessor edqsProcessor;
@ -92,7 +90,7 @@ public class KafkaEdqsStateService implements EdqsStateService {
consumer.commit(); consumer.commit();
}) })
.consumerCreator((config, partitionId) -> queueFactory.createEdqsMsgConsumer(EdqsQueue.STATE)) .consumerCreator((config, partitionId) -> queueFactory.createEdqsMsgConsumer(EdqsQueue.STATE))
.queueAdmin(queueAdmin) .queueAdmin(queueFactory.getEdqsQueueAdmin())
.consumerExecutor(eventConsumer.getConsumerExecutor()) .consumerExecutor(eventConsumer.getConsumerExecutor())
.taskExecutor(eventConsumer.getTaskExecutor()) .taskExecutor(eventConsumer.getTaskExecutor())
.scheduler(eventConsumer.getScheduler()) .scheduler(eventConsumer.getScheduler())

View File

@ -17,6 +17,7 @@ package org.thingsboard.server.queue.edqs;
import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueResponseTemplate; import org.thingsboard.server.queue.TbQueueResponseTemplate;
@ -32,4 +33,6 @@ public interface EdqsQueueFactory {
TbQueueResponseTemplate<TbProtoQueueMsg<ToEdqsMsg>, TbProtoQueueMsg<FromEdqsMsg>> createEdqsResponseTemplate(); TbQueueResponseTemplate<TbProtoQueueMsg<ToEdqsMsg>, TbProtoQueueMsg<FromEdqsMsg>> createEdqsResponseTemplate();
TbQueueAdmin getEdqsQueueAdmin();
} }

View File

@ -22,6 +22,7 @@ import org.thingsboard.server.common.stats.StatsFactory;
import org.thingsboard.server.common.stats.StatsType; import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueResponseTemplate; import org.thingsboard.server.queue.TbQueueResponseTemplate;
@ -39,6 +40,7 @@ public class InMemoryEdqsQueueFactory implements EdqsQueueFactory {
private final InMemoryStorage storage; private final InMemoryStorage storage;
private final EdqsConfig edqsConfig; private final EdqsConfig edqsConfig;
private final StatsFactory statsFactory; private final StatsFactory statsFactory;
private final TbQueueAdmin queueAdmin;
@Override @Override
public TbQueueConsumer<TbProtoQueueMsg<ToEdqsMsg>> createEdqsMsgConsumer(EdqsQueue queue) { public TbQueueConsumer<TbProtoQueueMsg<ToEdqsMsg>> createEdqsMsgConsumer(EdqsQueue queue) {
@ -76,4 +78,9 @@ public class InMemoryEdqsQueueFactory implements EdqsQueueFactory {
.build(); .build();
} }
@Override
public TbQueueAdmin getEdqsQueueAdmin() {
return queueAdmin;
}
} }

View File

@ -22,6 +22,7 @@ import org.thingsboard.server.common.stats.StatsType;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.FromEdqsMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToEdqsMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueResponseTemplate; import org.thingsboard.server.queue.TbQueueResponseTemplate;
@ -126,4 +127,9 @@ public class KafkaEdqsQueueFactory implements EdqsQueueFactory {
.build(); .build();
} }
@Override
public TbQueueAdmin getEdqsQueueAdmin() {
return edqsEventsAdmin;
}
} }

View File

@ -160,7 +160,7 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
int partition = record.partition(); int partition = record.partition();
Long endOffset = endOffsets.get(partition); Long endOffset = endOffsets.get(partition);
if (endOffset == null) { if (endOffset == null) {
log.warn("End offset not found for {} [{}]", record.topic(), partition); log.debug("End offset not found for {} [{}]", record.topic(), partition);
return; return;
} }
log.trace("[{}-{}] Got record offset {}, expected end offset: {}", record.topic(), partition, record.offset(), endOffset - 1); log.trace("[{}-{}] Got record offset {}, expected end offset: {}", record.topic(), partition, record.offset(), endOffset - 1);

View File

@ -138,6 +138,11 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE
return new InMemoryTbQueueConsumer<>(storage, topicService.buildTopicName(calculatedFieldSettings.getEventTopic())); return new InMemoryTbQueueConsumer<>(storage, topicService.buildTopicName(calculatedFieldSettings.getEventTopic()));
} }
@Override
public TbQueueAdmin getCalculatedFieldQueueAdmin() {
return queueAdmin;
}
@Override @Override
public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() { public TbQueueProducer<TbProtoQueueMsg<TransportProtos.ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() {
return new InMemoryTbQueueProducer<>(storage, topicService.buildTopicName(calculatedFieldSettings.getEventTopic())); return new InMemoryTbQueueProducer<>(storage, topicService.buildTopicName(calculatedFieldSettings.getEventTopic()));

View File

@ -526,6 +526,11 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi
return consumerBuilder.build(); return consumerBuilder.build();
} }
@Override
public TbQueueAdmin getCalculatedFieldQueueAdmin() {
return cfAdmin;
}
@Override @Override
public TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() { public TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() {
TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> requestBuilder = TbKafkaProducerTemplate.builder(); TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> requestBuilder = TbKafkaProducerTemplate.builder();

View File

@ -321,6 +321,11 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory {
return consumerBuilder.build(); return consumerBuilder.build();
} }
@Override
public TbQueueAdmin getCalculatedFieldQueueAdmin() {
return cfAdmin;
}
@Override @Override
public TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() { public TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer() {
TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> requestBuilder = TbKafkaProducerTemplate.builder(); TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder<TbProtoQueueMsg<ToCalculatedFieldMsg>> requestBuilder = TbKafkaProducerTemplate.builder();

View File

@ -29,6 +29,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToOtaPackageStateSer
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg;
import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.TbQueueRequestTemplate;
@ -122,6 +123,8 @@ public interface TbRuleEngineQueueFactory extends TbUsageStatsClientQueueFactory
TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgConsumer(); TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgConsumer();
TbQueueAdmin getCalculatedFieldQueueAdmin();
TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer(); TbQueueProducer<TbProtoQueueMsg<ToCalculatedFieldMsg>> createToCalculatedFieldMsgProducer();
TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgConsumer(); TbQueueConsumer<TbProtoQueueMsg<ToCalculatedFieldNotificationMsg>> createToCalculatedFieldNotificationMsgConsumer();

View File

@ -195,7 +195,9 @@ public class BaseTimeseriesService implements TimeseriesService {
} }
if (saveLatest) { if (saveLatest) {
latestFutures.add(Futures.transform(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry), version -> { latestFutures.add(Futures.transform(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry), version -> {
edqsService.onUpdate(tenantId, ObjectType.LATEST_TS_KV, new LatestTsKv(entityId, tsKvEntry, version)); if (version != null) {
edqsService.onUpdate(tenantId, ObjectType.LATEST_TS_KV, new LatestTsKv(entityId, tsKvEntry, version));
}
return version; return version;
}, MoreExecutors.directExecutor())); }, MoreExecutors.directExecutor()));
} }

View File

@ -15,17 +15,15 @@
limitations under the License. limitations under the License.
--> -->
<div class="flex max-w-sm flex-col gap-3 p-2"> <div class="flex w-96 flex-col gap-3 p-2">
<div class="tb-form-panel-title" translate>debug-settings.label</div> <div class="tb-form-panel-title" translate>debug-settings.label</div>
<div class="hint-container"> @if (debugLimitsConfiguration) {
<div class="tb-form-hint tb-primary-fill tb-flex center"> <div class="hint-container">
@if (debugLimitsConfiguration) { <div class="tb-form-hint tb-primary-fill tb-flex center">
{{ 'debug-settings.hint.main-limited' | translate: { entity: entityLabel ?? ('debug-settings.entity' | translate), msg: maxMessagesCount, time: (maxTimeFrameDuration | milliSecondsToTimeString: true : true) } }} {{ 'debug-settings.hint.main-limited' | translate: { entity: entityLabel ?? ('debug-settings.entity' | translate), msg: maxMessagesCount, time: (maxTimeFrameDuration | milliSecondsToTimeString: true : true) } }}
} @else { </div>
{{ 'debug-settings.hint.main' | translate }}
}
</div> </div>
</div> }
<div class="flex flex-col gap-3"> <div class="flex flex-col gap-3">
<mat-slide-toggle class="mat-slide" [formControl]="onFailuresControl"> <mat-slide-toggle class="mat-slide" [formControl]="onFailuresControl">
<div tb-hint-tooltip-icon="{{ 'debug-settings.hint.on-failure' | translate }}"> <div tb-hint-tooltip-icon="{{ 'debug-settings.hint.on-failure' | translate }}">
@ -33,12 +31,12 @@
</div> </div>
</mat-slide-toggle> </mat-slide-toggle>
<div class="align-center flex justify-between"> <div class="align-center flex justify-between">
<mat-slide-toggle class="mat-slide" [formControl]="debugAllControl"> <mat-slide-toggle class="mat-slide" [formControl]="debugAllControl" (change)="debugAllControl.markAsTouched()">
<div tb-hint-tooltip-icon="{{ 'debug-settings.hint.all-messages' | translate }}"> <div tb-hint-tooltip-icon="{{ 'debug-settings.hint.all-messages' | translate }}">
{{ 'debug-settings.all-messages' | translate: { time: (isDebugAllActive$ | async) && !allEnabled ? (allEnabledUntil | durationLeft) : (maxDebugModeDuration | milliSecondsToTimeString: true : true) } }} {{ 'debug-settings.all-messages' | translate: { time: (isDebugAllActive$ | async) && !allEnabled && debugAllControl.untouched ? (allEnabledUntil | durationLeft) : (maxDebugModeDuration | milliSecondsToTimeString: true : true) } }}
</div> </div>
</mat-slide-toggle> </mat-slide-toggle>
<button mat-icon-button *ngIf="(isDebugAllActive$ | async) && !allEnabled" <button mat-icon-button *ngIf="(isDebugAllActive$ | async) && !allEnabled && debugAllControl.untouched"
class="tb-mat-20" class="tb-mat-20"
matTooltip="{{ 'action.reset' | translate }}" matTooltip="{{ 'action.reset' | translate }}"
matTooltipPosition="above" matTooltipPosition="above"

View File

@ -127,19 +127,23 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
actionSourceId: [this.action.actionSourceId, Validators.required], actionSourceId: [this.action.actionSourceId, Validators.required],
columnIndex: [{value: this.checkColumnIndex(this.action.columnIndex), disabled: true}, Validators.required], columnIndex: [{value: this.checkColumnIndex(this.action.columnIndex), disabled: true}, Validators.required],
name: [this.action.name, [this.validateActionName(), Validators.required]], name: [this.action.name, [this.validateActionName(), Validators.required]],
buttonType: [isDefinedAndNotNull(this.action.buttonType) ? this.action.buttonType : WidgetHeaderActionButtonType.icon, []], buttonType: [{ value: this.action.buttonType ?? WidgetHeaderActionButtonType.icon, disabled: true}, []],
showIcon: [isDefinedAndNotNull(this.action.showIcon) ? this.action.showIcon : true, []], showIcon: [{ value: this.action.showIcon ?? true, disabled: true}, []],
icon: [this.action.icon, Validators.required], icon: [this.action.icon, Validators.required],
buttonColor: [isDefinedAndNotNull(this.action.buttonColor) ? this.action.buttonColor : 'rgba(0, 0, 0, 0.87)', []], buttonColor: [{ value: this.action.buttonColor ?? 'rgba(0, 0, 0, 0.87)', disabled: true}, []],
buttonFillColor: [isDefinedAndNotNull(this.action.buttonFillColor) ? this.action.buttonFillColor : '#3F52DD', []], buttonFillColor: [{ value: this.action.buttonFillColor ?? '#3F52DD', disabled: true}, []],
buttonBorderColor: [isDefinedAndNotNull(this.action.buttonBorderColor) ? this.action.buttonBorderColor : '#3F52DD', []], buttonBorderColor: [{ value: this.action.buttonBorderColor ?? '#3F52DD', disabled: true}, []],
customButtonStyle: [isDefinedAndNotNull(this.action.customButtonStyle) ? this.action.customButtonStyle : null, []], customButtonStyle: [{ value: this.action.customButtonStyle ?? {}, disabled: true}, []],
useShowWidgetActionFunction: [this.action.useShowWidgetActionFunction], useShowWidgetActionFunction: [this.action.useShowWidgetActionFunction],
showWidgetActionFunction: [this.action.showWidgetActionFunction || 'return true;'], showWidgetActionFunction: [this.action.showWidgetActionFunction || 'return true;'],
widgetAction: [actionDescriptorToAction(toWidgetActionDescriptor(this.action)), Validators.required] widgetAction: [actionDescriptorToAction(toWidgetActionDescriptor(this.action)), Validators.required]
}); });
this.updateShowWidgetActionForm(); this.updateShowWidgetActionForm();
this.widgetHeaderButtonValidators(); if (this.widgetActionFormGroup.get('actionSourceId').value === 'headerButton') {
this.widgetActionFormGroup.get('buttonType').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonColor').enable({emitEvent: false});
this.widgetHeaderButtonValidators();
}
this.widgetActionFormGroup.get('actionSourceId').valueChanges.pipe( this.widgetActionFormGroup.get('actionSourceId').valueChanges.pipe(
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)
).subscribe((value) => { ).subscribe((value) => {
@ -151,6 +155,17 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
} else { } else {
this.widgetActionFormGroup.get('columnIndex').disable(); this.widgetActionFormGroup.get('columnIndex').disable();
} }
if (value === 'headerButton') {
this.widgetActionFormGroup.get('buttonType').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonColor').enable({emitEvent: false});
this.widgetHeaderButtonValidators();
} else {
this.widgetActionFormGroup.get('buttonType').disable({emitEvent: false});
this.widgetActionFormGroup.get('showIcon').disable({emitEvent: false});
this.widgetActionFormGroup.get('buttonColor').disable({emitEvent: false});
this.widgetActionFormGroup.get('buttonFillColor').disable({emitEvent: false});
this.widgetActionFormGroup.get('buttonBorderColor').disable({emitEvent: false});
}
}); });
this.widgetActionFormGroup.get('useShowWidgetActionFunction').valueChanges.pipe( this.widgetActionFormGroup.get('useShowWidgetActionFunction').valueChanges.pipe(
takeUntilDestroyed(this.destroyRef) takeUntilDestroyed(this.destroyRef)
@ -185,10 +200,12 @@ export class WidgetActionDialogComponent extends DialogComponent<WidgetActionDia
this.widgetActionFormGroup.get('buttonFillColor').enable({emitEvent: false}); this.widgetActionFormGroup.get('buttonFillColor').enable({emitEvent: false});
break; break;
case WidgetHeaderActionButtonType.stroked: case WidgetHeaderActionButtonType.stroked:
this.widgetActionFormGroup.get('showIcon').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonBorderColor').enable({emitEvent: false});
break;
case WidgetHeaderActionButtonType.flat: case WidgetHeaderActionButtonType.flat:
this.widgetActionFormGroup.get('showIcon').enable({emitEvent: false}); this.widgetActionFormGroup.get('showIcon').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonFillColor').enable({emitEvent: false}); this.widgetActionFormGroup.get('buttonFillColor').enable({emitEvent: false});
this.widgetActionFormGroup.get('buttonBorderColor').enable({emitEvent: false});
break; break;
case WidgetHeaderActionButtonType.miniFab: case WidgetHeaderActionButtonType.miniFab:
this.widgetActionFormGroup.get('buttonFillColor').enable({emitEvent: false}); this.widgetActionFormGroup.get('buttonFillColor').enable({emitEvent: false});

View File

@ -109,11 +109,10 @@
[style]="action.customButtonStyle" [style]="action.customButtonStyle"
[class.!hidden]="isEdit" [class.!hidden]="isEdit"
[class.mr-2]="!last" [class.mr-2]="!last"
[style.background-color]="action.buttonFillColor"
(click)="action.onAction($event)" (click)="action.onAction($event)"
matTooltip="{{ action.displayName }}" matTooltip="{{ action.displayName }}"
matTooltipPosition="above"> matTooltipPosition="above">
<tb-icon [style.color]="action.buttonColor">{{ action.icon }}</tb-icon> <tb-icon>{{ action.icon }}</tb-icon>
</button> </button>
} }
@case (widgetHeaderActionButtonType.basic) { @case (widgetHeaderActionButtonType.basic) {
@ -123,46 +122,41 @@
(click)="action.onAction($event)" (click)="action.onAction($event)"
matTooltip="{{ action.displayName }}" matTooltip="{{ action.displayName }}"
matTooltipPosition="above"> matTooltipPosition="above">
<tb-icon matButtonIcon *ngIf="action.showIcon" [style.color]="action.buttonColor">{{ action.icon }}</tb-icon> <tb-icon matButtonIcon *ngIf="action.showIcon">{{ action.icon }}</tb-icon>
<span [style.color]="action.buttonColor">{{ action.displayName }}</span> <span>{{ action.displayName }}</span>
</button> </button>
} }
@case (widgetHeaderActionButtonType.raised) { @case (widgetHeaderActionButtonType.raised) {
<button [class.!hidden]="isEdit" mat-raised-button <button [class.!hidden]="isEdit" mat-raised-button
[class.mr-2]="!last" [class.mr-2]="!last"
[style]="action.customButtonStyle" [style]="action.customButtonStyle"
[style.background-color]="action.buttonFillColor"
(click)="action.onAction($event)" (click)="action.onAction($event)"
matTooltip="{{ action.displayName }}" matTooltip="{{ action.displayName }}"
matTooltipPosition="above"> matTooltipPosition="above">
<tb-icon matButtonIcon *ngIf="action.showIcon" [style.color]="action.buttonColor">{{ action.icon }}</tb-icon> <tb-icon matButtonIcon *ngIf="action.showIcon">{{ action.icon }}</tb-icon>
<span [style.color]="action.buttonColor">{{ action.displayName }}</span> <span>{{ action.displayName }}</span>
</button> </button>
} }
@case (widgetHeaderActionButtonType.stroked) { @case (widgetHeaderActionButtonType.stroked) {
<button [class.!hidden]="isEdit" mat-stroked-button <button [class.!hidden]="isEdit" mat-stroked-button
[class.mr-2]="!last" [class.mr-2]="!last"
[style]="action.customButtonStyle" [style]="action.customButtonStyle"
[style.border-color]="action.buttonBorderColor"
[style.background-color]="action.buttonFillColor"
(click)="action.onAction($event)" (click)="action.onAction($event)"
matTooltip="{{ action.displayName }}" matTooltip="{{ action.displayName }}"
matTooltipPosition="above"> matTooltipPosition="above">
<tb-icon matButtonIcon *ngIf="action.showIcon" [style.color]="action.buttonColor">{{ action.icon }}</tb-icon> <tb-icon matButtonIcon *ngIf="action.showIcon">{{ action.icon }}</tb-icon>
<span [style.color]="action.buttonColor">{{ action.displayName }}</span> <span>{{ action.displayName }}</span>
</button> </button>
} }
@case (widgetHeaderActionButtonType.flat) { @case (widgetHeaderActionButtonType.flat) {
<button [class.!hidden]="isEdit" mat-flat-button <button [class.!hidden]="isEdit" mat-flat-button
[class.mr-2]="!last" [class.mr-2]="!last"
[style]="action.customButtonStyle" [style]="action.customButtonStyle"
[style.background-color]="action.buttonFillColor"
[style.border-color]="action.buttonBorderColor"
(click)="action.onAction($event)" (click)="action.onAction($event)"
matTooltip="{{ action.displayName }}" matTooltip="{{ action.displayName }}"
matTooltipPosition="above"> matTooltipPosition="above">
<tb-icon matButtonIcon *ngIf="action.showIcon" [style.color]="action.buttonColor">{{ action.icon }}</tb-icon> <tb-icon matButtonIcon *ngIf="action.showIcon">{{ action.icon }}</tb-icon>
<span [style.color]="action.buttonColor">{{ action.displayName }}</span> <span>{{ action.displayName }}</span>
</button> </button>
} }
@default { @default {
@ -172,7 +166,7 @@
(click)="action.onAction($event)" (click)="action.onAction($event)"
matTooltip="{{ action.displayName }}" matTooltip="{{ action.displayName }}"
matTooltipPosition="above"> matTooltipPosition="above">
<tb-icon [style.color]="action.buttonColor">{{ action.icon }}</tb-icon> <tb-icon>{{ action.icon }}</tb-icon>
</button> </button>
} }
} }

View File

@ -44,6 +44,7 @@ import {
widgetActionSources, widgetActionSources,
WidgetActionType, WidgetActionType,
WidgetComparisonSettings, WidgetComparisonSettings,
WidgetHeaderActionButtonType,
WidgetMobileActionDescriptor, WidgetMobileActionDescriptor,
WidgetMobileActionType, WidgetMobileActionType,
WidgetResource, WidgetResource,
@ -301,10 +302,13 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
buttonType: descriptor.buttonType, buttonType: descriptor.buttonType,
showIcon: descriptor.showIcon, showIcon: descriptor.showIcon,
icon: descriptor.icon, icon: descriptor.icon,
buttonColor: descriptor.buttonColor, customButtonStyle: this.headerButtonStyle(
buttonFillColor: descriptor.buttonFillColor, descriptor.buttonType,
buttonBorderColor: descriptor.buttonBorderColor, descriptor.customButtonStyle,
customButtonStyle: descriptor.customButtonStyle, descriptor.buttonColor,
descriptor.buttonFillColor,
descriptor.buttonBorderColor
),
descriptor, descriptor,
useShowWidgetHeaderActionFunction, useShowWidgetHeaderActionFunction,
showWidgetHeaderActionFunction, showWidgetHeaderActionFunction,
@ -359,6 +363,39 @@ export class WidgetComponent extends PageComponent implements OnInit, OnChanges,
} }
} }
headerButtonStyle(buttonType: WidgetHeaderActionButtonType = WidgetHeaderActionButtonType.icon,
customButtonStyle:{[key: string]: string},
buttonColor: string = 'rgba(0,0,0,0.87)',
backgroundColor: string,
borderColor: string) {
const buttonStyle = {};
switch (buttonType) {
case WidgetHeaderActionButtonType.basic:
buttonStyle['--mdc-text-button-label-text-color'] = buttonColor;
break;
case WidgetHeaderActionButtonType.raised:
buttonStyle['--mdc-protected-button-label-text-color'] = buttonColor;
buttonStyle['--mdc-protected-button-container-color'] = backgroundColor;
break;
case WidgetHeaderActionButtonType.stroked:
buttonStyle['--mdc-outlined-button-label-text-color'] = buttonColor;
buttonStyle['--mdc-outlined-button-outline-color'] = borderColor;
break;
case WidgetHeaderActionButtonType.flat:
buttonStyle['--mdc-filled-button-label-text-color'] = buttonColor;
buttonStyle['--mdc-filled-button-container-color'] = backgroundColor;
break;
case WidgetHeaderActionButtonType.miniFab:
buttonStyle['--mat-fab-small-foreground-color'] = buttonColor;
buttonStyle['--mdc-fab-small-container-color'] = backgroundColor;
break;
default:
buttonStyle['--mat-icon-color'] = buttonColor;
break;
}
return {...buttonStyle, ...customButtonStyle};
}
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) { for (const propName of Object.keys(changes)) {
const change = changes[propName]; const change = changes[propName];

View File

@ -132,7 +132,7 @@ export interface WidgetHeaderAction extends IWidgetAction {
buttonColor?: string; buttonColor?: string;
buttonFillColor?: string; buttonFillColor?: string;
buttonBorderColor?: string; buttonBorderColor?: string;
customButtonStyle?: string; customButtonStyle?: {[key: string]: string};
useShowWidgetHeaderActionFunction: boolean; useShowWidgetHeaderActionFunction: boolean;
showWidgetHeaderActionFunction: CompiledTbFunction<ShowWidgetHeaderActionFunction>; showWidgetHeaderActionFunction: CompiledTbFunction<ShowWidgetHeaderActionFunction>;
} }

View File

@ -45,9 +45,9 @@
<tb-calculated-fields-table [active]="calculatedFieldsTab.isActive" [entityId]="entity.id" [entityName]="entity.name"/> <tb-calculated-fields-table [active]="calculatedFieldsTab.isActive" [entityId]="entity.id" [entityName]="entity.name"/>
</mat-tab> </mat-tab>
<mat-tab *ngIf="entity" #alarmRules="matTab" <mat-tab *ngIf="entity" #alarmRules="matTab"
label="{{'device-profile.alarm-rules-with-count' | translate: label="{{ this.detailsForm.get('profileData.alarms').value?.length
{count: this.detailsForm.get('profileData.alarms').value?.length ? this.detailsForm.get('profileData.alarms').value.length : 0} ? ('device-profile.alarm-rules-with-count' | translate: { count: this.detailsForm.get('profileData.alarms').value.length })
}}"> : 'device-profile.alarm-rules' | translate }}">
<div class="mat-padding" [formGroup]="detailsForm" *ngIf="alarmRules.isActive"> <div class="mat-padding" [formGroup]="detailsForm" *ngIf="alarmRules.isActive">
<div formGroupName="profileData"> <div formGroupName="profileData">
<tb-device-profile-alarms formControlName="alarms" [deviceProfileId]="entity.id"></tb-device-profile-alarms> <tb-device-profile-alarms formControlName="alarms" [deviceProfileId]="entity.id"></tb-device-profile-alarms>

View File

@ -780,7 +780,7 @@ export interface WidgetActionDescriptor extends WidgetAction {
buttonColor?: string; buttonColor?: string;
buttonFillColor?: string; buttonFillColor?: string;
buttonBorderColor?: string; buttonBorderColor?: string;
customButtonStyle?: string; customButtonStyle?: {[key: string]: string};
displayName?: string; displayName?: string;
useShowWidgetActionFunction?: boolean; useShowWidgetActionFunction?: boolean;
showWidgetActionFunction?: TbFunction; showWidgetActionFunction?: TbFunction;

View File

@ -991,7 +991,7 @@
"type-sms-sent": "SMS sent" "type-sms-sent": "SMS sent"
}, },
"debug-settings": { "debug-settings": {
"label": "Debug configuration", "label": "Debug Configuration",
"on-failure": "Failures only (24/7)", "on-failure": "Failures only (24/7)",
"all-messages": "All messages ({{time}})", "all-messages": "All messages ({{time}})",
"failures": "Failures", "failures": "Failures",
@ -999,10 +999,9 @@
"rule-node": "rule node", "rule-node": "rule node",
"calculated-field": "calculated field", "calculated-field": "calculated field",
"hint": { "hint": {
"main": "All node debug messages rate limited with:", "main-limited": "No more than {{msg}} {{entity}} debug messages per {{time}} will be recorded.",
"main-limited": "All {{entity}} debug messages will be rate-limited, with a maximum of {{msg}} messages allowed per {{time}}.", "on-failure": "Log all debug messages.",
"on-failure": "Save all failure debug events without time limit.", "all-messages": "Log error messages only. "
"all-messages": "Save all debug events during time limit."
} }
}, },
"calculated-fields": { "calculated-fields": {