Browse Source

Merge remote-tracking branch 'upstream/master' into improvement/device-credential/select-type

pull/4871/head
Vladyslav_Prykhodko 5 years ago
parent
commit
bccbecadf4
  1. 10
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  2. 3
      application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java
  3. 82
      application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java
  4. 2
      application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java
  5. 14
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  6. 6
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java
  7. 1
      application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java
  8. 2
      application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java
  9. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java
  10. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java
  11. 5
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java
  12. 2
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java
  13. 6
      dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java
  14. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java
  15. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java
  16. 11
      dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java
  17. 13
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java
  18. 84
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java
  19. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
  20. 44
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java
  21. 8
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java
  22. 649
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java
  23. 23
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts
  24. 3
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  25. 79
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html
  26. 23
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss
  27. 153
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts
  28. 76
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html
  29. 31
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts
  30. 42
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts
  31. 5
      ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html
  32. 8
      ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts
  33. 5
      ui-ngx/src/app/shared/models/device.models.ts
  34. 15
      ui-ngx/src/app/shared/models/query/query.models.ts
  35. 4
      ui-ngx/src/assets/locale/locale.constant-en_US.json

10
application/src/main/java/org/thingsboard/server/controller/AuthController.java

@ -135,7 +135,7 @@ public class AuthController extends BaseController {
} }
} }
@RequestMapping(value = "/noauth/activate", params = { "activateToken" }, method = RequestMethod.GET) @RequestMapping(value = "/noauth/activate", params = {"activateToken"}, method = RequestMethod.GET)
public ResponseEntity<String> checkActivateToken( public ResponseEntity<String> checkActivateToken(
@RequestParam(value = "activateToken") String activateToken) { @RequestParam(value = "activateToken") String activateToken) {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
@ -159,7 +159,7 @@ public class AuthController extends BaseController {
@RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST) @RequestMapping(value = "/noauth/resetPasswordByEmail", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK) @ResponseStatus(value = HttpStatus.OK)
public void requestResetPasswordByEmail ( public void requestResetPasswordByEmail(
@RequestBody JsonNode resetPasswordByEmailRequest, @RequestBody JsonNode resetPasswordByEmailRequest,
HttpServletRequest request) throws ThingsboardException { HttpServletRequest request) throws ThingsboardException {
try { try {
@ -170,13 +170,13 @@ public class AuthController extends BaseController {
String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl, String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl,
userCredentials.getResetToken()); userCredentials.getResetToken());
mailService.sendResetPasswordEmail(resetUrl, email); mailService.sendResetPasswordEmailAsync(resetUrl, email);
} catch (Exception e) { } catch (Exception e) {
throw handleException(e); log.warn("Error occurred: {}", e.getMessage());
} }
} }
@RequestMapping(value = "/noauth/resetPassword", params = { "resetToken" }, method = RequestMethod.GET) @RequestMapping(value = "/noauth/resetPassword", params = {"resetToken"}, method = RequestMethod.GET)
public ResponseEntity<String> checkResetToken( public ResponseEntity<String> checkResetToken(
@RequestParam(value = "resetToken") String resetToken) { @RequestParam(value = "resetToken") String resetToken) {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();

3
application/src/main/java/org/thingsboard/server/exception/ThingsboardErrorResponseHandler.java

@ -25,6 +25,7 @@ import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException; import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.LockedException; import org.springframework.security.authentication.LockedException;
import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice; import org.springframework.web.bind.annotation.RestControllerAdvice;
@ -152,7 +153,7 @@ public class ThingsboardErrorResponseHandler extends ResponseEntityExceptionHand
private void handleAuthenticationException(AuthenticationException authenticationException, HttpServletResponse response) throws IOException { private void handleAuthenticationException(AuthenticationException authenticationException, HttpServletResponse response) throws IOException {
response.setStatus(HttpStatus.UNAUTHORIZED.value()); response.setStatus(HttpStatus.UNAUTHORIZED.value());
if (authenticationException instanceof BadCredentialsException) { if (authenticationException instanceof BadCredentialsException || authenticationException instanceof UsernameNotFoundException) {
mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of("Invalid username or password", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));
} else if (authenticationException instanceof DisabledException) { } else if (authenticationException instanceof DisabledException) {
mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED)); mapper.writeValue(response.getWriter(), ThingsboardErrorResponse.of("User account is not active", ThingsboardErrorCode.AUTHENTICATION, HttpStatus.UNAUTHORIZED));

82
application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.service.install.update; package org.thingsboard.server.service.install.update;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
@ -31,6 +32,7 @@ import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmInfo; import org.thingsboard.server.common.data.alarm.AlarmInfo;
import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
@ -41,16 +43,21 @@ import org.thingsboard.server.common.data.oauth2.deprecated.OAuth2ClientsParams;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.alarm.AlarmDao;
import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.alarm.AlarmService;
import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entity.EntityService;
import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.model.sql.DeviceProfileEntity;
import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.dao.oauth2.OAuth2Service;
import org.thingsboard.server.dao.oauth2.OAuth2Utils; import org.thingsboard.server.dao.oauth2.OAuth2Utils;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.rule.RuleChainService;
import org.thingsboard.server.dao.sql.device.DeviceProfileRepository;
import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.tenant.TenantService;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.service.install.InstallScripts;
@ -92,6 +99,9 @@ public class DefaultDataUpdateService implements DataUpdateService {
@Autowired @Autowired
private AlarmDao alarmDao; private AlarmDao alarmDao;
@Autowired
private DeviceProfileRepository deviceProfileRepository;
@Autowired @Autowired
private OAuth2Service oAuth2Service; private OAuth2Service oAuth2Service;
@ -114,6 +124,7 @@ public class DefaultDataUpdateService implements DataUpdateService {
log.info("Updating data from version 3.2.2 to 3.3.0 ..."); log.info("Updating data from version 3.2.2 to 3.3.0 ...");
tenantsDefaultEdgeRuleChainUpdater.updateEntities(null); tenantsDefaultEdgeRuleChainUpdater.updateEntities(null);
tenantsAlarmsCustomerUpdater.updateEntities(null); tenantsAlarmsCustomerUpdater.updateEntities(null);
deviceProfileEntityDynamicConditionsUpdater.updateEntities(null);
updateOAuth2Params(); updateOAuth2Params();
break; break;
default: default:
@ -121,6 +132,45 @@ public class DefaultDataUpdateService implements DataUpdateService {
} }
} }
private final PaginatedUpdater<String, DeviceProfileEntity> deviceProfileEntityDynamicConditionsUpdater =
new PaginatedUpdater<>() {
@Override
protected String getName() {
return "Device Profile Entity Dynamic Conditions Updater";
}
@Override
protected PageData<DeviceProfileEntity> findEntities(String id, PageLink pageLink) {
return DaoUtil.pageToPageData(deviceProfileRepository.findAll(DaoUtil.toPageable(pageLink)));
}
@Override
protected void updateEntity(DeviceProfileEntity deviceProfile) {
if (deviceProfile.getProfileData().has("alarms") &&
!deviceProfile.getProfileData().get("alarms").isNull()) {
boolean isUpdated = false;
JsonNode array = deviceProfile.getProfileData().get("alarms");
for (JsonNode node : array) {
if (node.has("createRules")) {
JsonNode createRules = node.get("createRules");
for (AlarmSeverity severity : AlarmSeverity.values()) {
if (createRules.has(severity.name())) {
isUpdated = isUpdated || convertDeviceProfileAlarmRulesForVersion330(createRules.get(severity.name()).get("condition").get("spec"));
}
}
}
if (node.has("clearRule") && !node.get("clearRule").isNull()) {
isUpdated = isUpdated || convertDeviceProfileAlarmRulesForVersion330(node.get("clearRule").get("condition").get("spec"));
}
}
if (isUpdated) {
deviceProfileRepository.save(deviceProfile);
}
}
}
};
private final PaginatedUpdater<String, Tenant> tenantsDefaultRuleChainUpdater = private final PaginatedUpdater<String, Tenant> tenantsDefaultRuleChainUpdater =
new PaginatedUpdater<>() { new PaginatedUpdater<>() {
@ -370,6 +420,33 @@ public class DefaultDataUpdateService implements DataUpdateService {
} }
} }
private boolean convertDeviceProfileAlarmRulesForVersion330(JsonNode spec) {
if (spec != null) {
if (spec.has("type") && spec.get("type").asText().equals("DURATION")) {
if (spec.has("value")) {
long value = spec.get("value").asLong();
var predicate = new FilterPredicateValue<>(
value, null, new DynamicValue<>(null, null, false)
);
((ObjectNode) spec).remove("value");
((ObjectNode) spec).putPOJO("predicate", predicate);
return true;
}
} else if (spec.has("type") && spec.get("type").asText().equals("REPEATING")) {
if (spec.has("count")) {
int count = spec.get("count").asInt();
var predicate = new FilterPredicateValue<>(
count, null, new DynamicValue<>(null, null, false)
);
((ObjectNode) spec).remove("count");
((ObjectNode) spec).putPOJO("predicate", predicate);
return true;
}
}
}
return false;
}
private void updateOAuth2Params() { private void updateOAuth2Params() {
try { try {
OAuth2ClientsParams oauth2ClientsParams = oAuth2Service.findOAuth2Params(); OAuth2ClientsParams oauth2ClientsParams = oAuth2Service.findOAuth2Params();
@ -380,9 +457,8 @@ public class DefaultDataUpdateService implements DataUpdateService {
oAuth2Service.saveOAuth2Params(new OAuth2ClientsParams(false, Collections.emptyList())); oAuth2Service.saveOAuth2Params(new OAuth2ClientsParams(false, Collections.emptyList()));
log.info("Successfully updated OAuth2 parameters!"); log.info("Successfully updated OAuth2 parameters!");
} }
} } catch (Exception e) {
catch (Exception e) { log.error("Failed to update OAuth2 parameters", e);
log.error("Failed to update OAuth2 parameters", e);
} }
} }

2
application/src/main/java/org/thingsboard/server/service/install/update/PaginatedUpdater.java

@ -22,7 +22,7 @@ import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
@Slf4j @Slf4j
public abstract class PaginatedUpdater<I, D extends SearchTextBased<? extends UUIDBased>> { public abstract class PaginatedUpdater<I, D> {
private static final int DEFAULT_LIMIT = 100; private static final int DEFAULT_LIMIT = 100;
private int updated = 0; private int updated = 0;

14
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -73,6 +73,9 @@ public class DefaultMailService implements MailService {
@Autowired @Autowired
private TbApiUsageStateService apiUsageStateService; private TbApiUsageStateService apiUsageStateService;
@Autowired
private MailExecutorService mailExecutorService;
private JavaMailSenderImpl mailSender; private JavaMailSenderImpl mailSender;
private String mailFrom; private String mailFrom;
@ -221,6 +224,17 @@ public class DefaultMailService implements MailService {
sendMail(mailSender, mailFrom, email, subject, message); sendMail(mailSender, mailFrom, email, subject, message);
} }
@Override
public void sendResetPasswordEmailAsync(String passwordResetLink, String email) {
mailExecutorService.execute(() -> {
try {
this.sendResetPasswordEmail(passwordResetLink, email);
} catch (ThingsboardException e) {
log.error("Error occurred: {} ", e.getMessage());
}
});
}
@Override @Override
public void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException { public void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException {

6
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java

@ -206,11 +206,7 @@ public abstract class AbstractOAuth2ClientMapper {
} }
private Optional<DashboardId> getDashboardId(TenantId tenantId, String dashboardName) { private Optional<DashboardId> getDashboardId(TenantId tenantId, String dashboardName) {
PageLink searchTextLink = new PageLink(1, 0, dashboardName); return Optional.ofNullable(dashboardService.findFirstDashboardInfoByTenantIdAndName(tenantId, dashboardName)).map(IdBased::getId);
PageData<DashboardInfo> dashboardsPage = dashboardService.findDashboardsByTenantId(tenantId, searchTextLink);
return dashboardsPage.getData().stream()
.findAny()
.map(IdBased::getId);
} }
private Optional<DashboardId> getDashboardId(TenantId tenantId, CustomerId customerId, String dashboardName) { private Optional<DashboardId> getDashboardId(TenantId tenantId, CustomerId customerId, String dashboardName) {

1
application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java

@ -155,6 +155,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest {
doPost("/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest) doPost("/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest)
.andExpect(status().isOk()); .andExpect(status().isOk());
Thread.sleep(1000);
doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken) doGet("/api/noauth/resetPassword?resetToken={resetToken}", TestMailService.currentResetPasswordToken)
.andExpect(status().isSeeOther()) .andExpect(status().isSeeOther())
.andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + TestMailService.currentResetPasswordToken)); .andExpect(header().string(HttpHeaders.LOCATION, "/login/resetPassword?resetToken=" + TestMailService.currentResetPasswordToken));

2
application/src/test/java/org/thingsboard/server/service/mail/TestMailService.java

@ -51,7 +51,7 @@ public class TestMailService {
currentResetPasswordToken = passwordResetLink.split("=")[1]; currentResetPasswordToken = passwordResetLink.split("=")[1];
return null; return null;
} }
}).when(mailService).sendResetPasswordEmail(Mockito.anyString(), Mockito.anyString()); }).when(mailService).sendResetPasswordEmailAsync(Mockito.anyString(), Mockito.anyString());
return mailService; return mailService;
} }

2
common/dao-api/src/main/java/org/thingsboard/server/dao/dashboard/DashboardService.java

@ -58,4 +58,6 @@ public interface DashboardService {
Dashboard unassignDashboardFromEdge(TenantId tenantId, DashboardId dashboardId, EdgeId edgeId); Dashboard unassignDashboardFromEdge(TenantId tenantId, DashboardId dashboardId, EdgeId edgeId);
PageData<DashboardInfo> findDashboardsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink); PageData<DashboardInfo> findDashboardsByTenantIdAndEdgeId(TenantId tenantId, EdgeId edgeId, PageLink pageLink);
DashboardInfo findFirstDashboardInfoByTenantIdAndName(TenantId tenantId, String name);
} }

3
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DurationAlarmConditionSpec.java

@ -17,6 +17,7 @@ package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -25,7 +26,7 @@ import java.util.concurrent.TimeUnit;
public class DurationAlarmConditionSpec implements AlarmConditionSpec { public class DurationAlarmConditionSpec implements AlarmConditionSpec {
private TimeUnit unit; private TimeUnit unit;
private long value; private FilterPredicateValue<Long> predicate;
@Override @Override
public AlarmConditionSpecType getType() { public AlarmConditionSpecType getType() {

5
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/RepeatingAlarmConditionSpec.java

@ -17,14 +17,13 @@ package org.thingsboard.server.common.data.device.profile;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import java.util.concurrent.TimeUnit;
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
public class RepeatingAlarmConditionSpec implements AlarmConditionSpec { public class RepeatingAlarmConditionSpec implements AlarmConditionSpec {
private int count; private FilterPredicateValue<Integer> predicate;
@Override @Override
public AlarmConditionSpecType getType() { public AlarmConditionSpecType getType() {

2
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardInfoDao.java

@ -56,4 +56,6 @@ public interface DashboardInfoDao extends Dao<DashboardInfo> {
*/ */
PageData<DashboardInfo> findDashboardsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, PageLink pageLink); PageData<DashboardInfo> findDashboardsByTenantIdAndEdgeId(UUID tenantId, UUID edgeId, PageLink pageLink);
DashboardInfo findFirstByTenantIdAndName(UUID tenantId, String name);
} }

6
dao/src/main/java/org/thingsboard/server/dao/dashboard/DashboardServiceImpl.java

@ -34,7 +34,6 @@ import org.thingsboard.server.common.data.id.EdgeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration;
@ -269,6 +268,11 @@ public class DashboardServiceImpl extends AbstractEntityService implements Dashb
return dashboardInfoDao.findDashboardsByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink); return dashboardInfoDao.findDashboardsByTenantIdAndEdgeId(tenantId.getId(), edgeId.getId(), pageLink);
} }
@Override
public DashboardInfo findFirstDashboardInfoByTenantIdAndName(TenantId tenantId, String name) {
return dashboardInfoDao.findFirstByTenantIdAndName(tenantId.getId(), name);
}
private DataValidator<Dashboard> dashboardValidator = private DataValidator<Dashboard> dashboardValidator =
new DataValidator<Dashboard>() { new DataValidator<Dashboard>() {
@Override @Override

2
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/DashboardInfoRepository.java

@ -29,6 +29,8 @@ import java.util.UUID;
*/ */
public interface DashboardInfoRepository extends PagingAndSortingRepository<DashboardInfoEntity, UUID> { public interface DashboardInfoRepository extends PagingAndSortingRepository<DashboardInfoEntity, UUID> {
DashboardInfoEntity findFirstByTenantIdAndTitle(UUID tenantId, String title);
@Query("SELECT di FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId " + @Query("SELECT di FROM DashboardInfoEntity di WHERE di.tenantId = :tenantId " +
"AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") "AND LOWER(di.searchText) LIKE LOWER(CONCAT(:searchText, '%'))")
Page<DashboardInfoEntity> findByTenantId(@Param("tenantId") UUID tenantId, Page<DashboardInfoEntity> findByTenantId(@Param("tenantId") UUID tenantId,

5
dao/src/main/java/org/thingsboard/server/dao/sql/dashboard/JpaDashboardInfoDao.java

@ -83,4 +83,9 @@ public class JpaDashboardInfoDao extends JpaAbstractSearchTextDao<DashboardInfoE
Objects.toString(pageLink.getTextSearch(), ""), Objects.toString(pageLink.getTextSearch(), ""),
DaoUtil.toPageable(pageLink))); DaoUtil.toPageable(pageLink)));
} }
@Override
public DashboardInfo findFirstByTenantIdAndName(UUID tenantId, String name) {
return DaoUtil.getData(dashboardInfoRepository.findFirstByTenantIdAndTitle(tenantId, name));
}
} }

11
dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java

@ -25,7 +25,10 @@ import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy; import org.springframework.context.annotation.Lazy;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.Tenant;
@ -49,7 +52,6 @@ import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TbTenantProfileCache;
import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.tenant.TenantDao;
import org.thingsboard.common.util.JacksonUtil;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -194,11 +196,11 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
DataValidator.validateEmail(email); DataValidator.validateEmail(email);
User user = userDao.findByEmail(tenantId, email); User user = userDao.findByEmail(tenantId, email);
if (user == null) { if (user == null) {
throw new IncorrectParameterException(String.format("Unable to find user by email [%s]", email)); throw new UsernameNotFoundException(String.format("Unable to find user by email [%s]", email));
} }
UserCredentials userCredentials = userCredentialsDao.findByUserId(tenantId, user.getUuidId()); UserCredentials userCredentials = userCredentialsDao.findByUserId(tenantId, user.getUuidId());
if (!userCredentials.isEnabled()) { if (!userCredentials.isEnabled()) {
throw new IncorrectParameterException("Unable to reset password for inactive user"); throw new DisabledException(String.format("User credentials not enabled [%s]", email));
} }
userCredentials.setResetToken(RandomStringUtils.randomAlphanumeric(DEFAULT_TOKEN_LENGTH)); userCredentials.setResetToken(RandomStringUtils.randomAlphanumeric(DEFAULT_TOKEN_LENGTH));
return saveUserCredentials(tenantId, userCredentials); return saveUserCredentials(tenantId, userCredentials);
@ -365,7 +367,8 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic
JsonNode userPasswordHistoryJson; JsonNode userPasswordHistoryJson;
if (additionalInfo.has(USER_PASSWORD_HISTORY)) { if (additionalInfo.has(USER_PASSWORD_HISTORY)) {
userPasswordHistoryJson = additionalInfo.get(USER_PASSWORD_HISTORY); userPasswordHistoryJson = additionalInfo.get(USER_PASSWORD_HISTORY);
userPasswordHistoryMap = JacksonUtil.convertValue(userPasswordHistoryJson, new TypeReference<>(){}); userPasswordHistoryMap = JacksonUtil.convertValue(userPasswordHistoryJson, new TypeReference<>() {
});
} }
if (userPasswordHistoryMap != null) { if (userPasswordHistoryMap != null) {
userPasswordHistoryMap.put(Long.toString(System.currentTimeMillis()), userCredentials.getPassword()); userPasswordHistoryMap.put(Long.toString(System.currentTimeMillis()), userCredentials.getPassword());

13
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/MailService.java

@ -31,22 +31,25 @@ public interface MailService {
void updateMailConfiguration(); void updateMailConfiguration();
void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException; void sendEmail(TenantId tenantId, String email, String subject, String message) throws ThingsboardException;
void sendTestMail(JsonNode config, String email) throws ThingsboardException; void sendTestMail(JsonNode config, String email) throws ThingsboardException;
void sendActivationEmail(String activationLink, String email) throws ThingsboardException; void sendActivationEmail(String activationLink, String email) throws ThingsboardException;
void sendAccountActivatedEmail(String loginLink, String email) throws ThingsboardException; void sendAccountActivatedEmail(String loginLink, String email) throws ThingsboardException;
void sendResetPasswordEmail(String passwordResetLink, String email) throws ThingsboardException; void sendResetPasswordEmail(String passwordResetLink, String email) throws ThingsboardException;
void sendResetPasswordEmailAsync(String passwordResetLink, String email);
void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException; void sendPasswordWasResetEmail(String loginLink, String email) throws ThingsboardException;
void sendAccountLockoutEmail( String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException; void sendAccountLockoutEmail(String lockoutEmail, String email, Integer maxFailedLoginAttempts) throws ThingsboardException;
void send(TenantId tenantId, CustomerId customerId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map<String, String> images) throws ThingsboardException; void send(TenantId tenantId, CustomerId customerId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map<String, String> images) throws ThingsboardException;
void send(TenantId tenantId, CustomerId customerId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map<String, String> images, JavaMailSender javaMailSender) throws ThingsboardException; void send(TenantId tenantId, CustomerId customerId, String from, String to, String cc, String bcc, String subject, String body, boolean isHtml, Map<String, String> images, JavaMailSender javaMailSender) throws ThingsboardException;
void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException; void sendApiFeatureStateEmail(ApiFeature apiFeature, ApiUsageStateValue stateValue, String email, ApiUsageStateMailMessage msg) throws ThingsboardException;
} }

84
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java

@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmConditionSpec; import org.thingsboard.server.common.data.device.profile.AlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType;
import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.CustomTimeSchedule; import org.thingsboard.server.common.data.device.profile.CustomTimeSchedule;
import org.thingsboard.server.common.data.device.profile.CustomTimeScheduleItem; import org.thingsboard.server.common.data.device.profile.CustomTimeScheduleItem;
@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpe
import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule; import org.thingsboard.server.common.data.device.profile.SpecificTimeSchedule;
import org.thingsboard.server.common.data.query.BooleanFilterPredicate; import org.thingsboard.server.common.data.query.BooleanFilterPredicate;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate; import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.FilterPredicateValue; import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilterPredicate; import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.NumericFilterPredicate; import org.thingsboard.server.common.data.query.NumericFilterPredicate;
@ -43,6 +45,7 @@ import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.Set; import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
@Data @Data
@ -52,8 +55,6 @@ class AlarmRuleState {
private final AlarmSeverity severity; private final AlarmSeverity severity;
private final AlarmRule alarmRule; private final AlarmRule alarmRule;
private final AlarmConditionSpec spec; private final AlarmConditionSpec spec;
private final long requiredDurationInMs;
private final long requiredRepeats;
private final Set<AlarmConditionFilterKey> entityKeys; private final Set<AlarmConditionFilterKey> entityKeys;
private PersistedAlarmRuleState state; private PersistedAlarmRuleState state;
private boolean updateFlag; private boolean updateFlag;
@ -69,20 +70,6 @@ class AlarmRuleState {
this.state = new PersistedAlarmRuleState(0L, 0L, 0L); this.state = new PersistedAlarmRuleState(0L, 0L, 0L);
} }
this.spec = getSpec(alarmRule); this.spec = getSpec(alarmRule);
long requiredDurationInMs = 0;
long requiredRepeats = 0;
switch (spec.getType()) {
case DURATION:
DurationAlarmConditionSpec duration = (DurationAlarmConditionSpec) spec;
requiredDurationInMs = duration.getUnit().toMillis(duration.getValue());
break;
case REPEATING:
RepeatingAlarmConditionSpec repeating = (RepeatingAlarmConditionSpec) spec;
requiredRepeats = repeating.getCount();
break;
}
this.requiredDurationInMs = requiredDurationInMs;
this.requiredRepeats = requiredRepeats;
this.dynamicPredicateValueCtx = dynamicPredicateValueCtx; this.dynamicPredicateValueCtx = dynamicPredicateValueCtx;
} }
@ -211,6 +198,7 @@ class AlarmRuleState {
if (active && eval(alarmRule.getCondition(), data)) { if (active && eval(alarmRule.getCondition(), data)) {
state.setEventCount(state.getEventCount() + 1); state.setEventCount(state.getEventCount() + 1);
updateFlag = true; updateFlag = true;
long requiredRepeats = resolveRequiredRepeats(data);
return state.getEventCount() >= requiredRepeats ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE; return state.getEventCount() >= requiredRepeats ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE;
} else { } else {
return AlarmEvalResult.FALSE; return AlarmEvalResult.FALSE;
@ -230,18 +218,62 @@ class AlarmRuleState {
state.setDuration(0L); state.setDuration(0L);
updateFlag = true; updateFlag = true;
} }
long requiredDurationInMs = resolveRequiredDurationInMs(data);
return state.getDuration() > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE; return state.getDuration() > requiredDurationInMs ? AlarmEvalResult.TRUE : AlarmEvalResult.NOT_YET_TRUE;
} else { } else {
return AlarmEvalResult.FALSE; return AlarmEvalResult.FALSE;
} }
} }
public AlarmEvalResult eval(long ts) { private long resolveRequiredRepeats(DataSnapshot data) {
long repeatingTimes = 0;
AlarmConditionSpec alarmConditionSpec = getSpec();
AlarmConditionSpecType specType = alarmConditionSpec.getType();
if(specType.equals(AlarmConditionSpecType.REPEATING)) {
RepeatingAlarmConditionSpec repeating = (RepeatingAlarmConditionSpec) spec;
repeatingTimes = repeating.getPredicate().getDefaultValue();
if (repeating.getPredicate().getDynamicValue() != null &&
repeating.getPredicate().getDynamicValue().getSourceAttribute() != null) {
EntityKeyValue repeatingKeyValue = getDynamicPredicateValue(data, repeating.getPredicate().getDynamicValue());
if (repeatingKeyValue != null) {
repeatingTimes = repeatingKeyValue.getLngValue();
}
}
}
return repeatingTimes;
}
private long resolveRequiredDurationInMs(DataSnapshot data) {
long durationTimeInMs = 0;
AlarmConditionSpec alarmConditionSpec = getSpec();
AlarmConditionSpecType specType = alarmConditionSpec.getType();
if(specType.equals(AlarmConditionSpecType.DURATION)) {
DurationAlarmConditionSpec duration = (DurationAlarmConditionSpec) spec;
TimeUnit timeUnit = duration.getUnit();
durationTimeInMs = timeUnit.toMillis(duration.getPredicate().getDefaultValue());
if (duration.getPredicate().getDynamicValue() != null &&
duration.getPredicate().getDynamicValue().getSourceAttribute() != null) {
EntityKeyValue durationKeyValue = getDynamicPredicateValue(data, duration.getPredicate().getDynamicValue());
if (durationKeyValue != null) {
durationTimeInMs = timeUnit.toMillis(durationKeyValue.getLngValue());
}
}
}
return durationTimeInMs;
}
public AlarmEvalResult eval(long ts, DataSnapshot dataSnapshot) {
switch (spec.getType()) { switch (spec.getType()) {
case SIMPLE: case SIMPLE:
case REPEATING: case REPEATING:
return AlarmEvalResult.NOT_YET_TRUE; return AlarmEvalResult.NOT_YET_TRUE;
case DURATION: case DURATION:
long requiredDurationInMs = resolveRequiredDurationInMs(dataSnapshot);
if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) { if (requiredDurationInMs > 0 && state.getLastEventTs() > 0 && ts > state.getLastEventTs()) {
long duration = state.getDuration() + (ts - state.getLastEventTs()); long duration = state.getDuration() + (ts - state.getLastEventTs());
if (isActive(ts)) { if (isActive(ts)) {
@ -411,7 +443,7 @@ class AlarmRuleState {
} }
private <T> T getPredicateValue(DataSnapshot data, FilterPredicateValue<T> value, AlarmConditionFilter filter, Function<EntityKeyValue, T> transformFunction) { private <T> T getPredicateValue(DataSnapshot data, FilterPredicateValue<T> value, AlarmConditionFilter filter, Function<EntityKeyValue, T> transformFunction) {
EntityKeyValue ekv = getDynamicPredicateValue(data, value); EntityKeyValue ekv = getDynamicPredicateValue(data, value.getDynamicValue());
if (ekv != null) { if (ekv != null) {
T result = transformFunction.apply(ekv); T result = transformFunction.apply(ekv);
if (result != null) { if (result != null) {
@ -425,22 +457,22 @@ class AlarmRuleState {
} }
} }
private <T> EntityKeyValue getDynamicPredicateValue(DataSnapshot data, FilterPredicateValue<T> value) { private <T> EntityKeyValue getDynamicPredicateValue(DataSnapshot data, DynamicValue<T> value) {
EntityKeyValue ekv = null; EntityKeyValue ekv = null;
if (value.getDynamicValue() != null) { if (value != null) {
switch (value.getDynamicValue().getSourceType()) { switch (value.getSourceType()) {
case CURRENT_DEVICE: case CURRENT_DEVICE:
ekv = data.getValue(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, value.getDynamicValue().getSourceAttribute())); ekv = data.getValue(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, value.getSourceAttribute()));
if (ekv != null || !value.getDynamicValue().isInherit()) { if (ekv != null || !value.isInherit()) {
break; break;
} }
case CURRENT_CUSTOMER: case CURRENT_CUSTOMER:
ekv = dynamicPredicateValueCtx.getCustomerValue(value.getDynamicValue().getSourceAttribute()); ekv = dynamicPredicateValueCtx.getCustomerValue(value.getSourceAttribute());
if (ekv != null || !value.getDynamicValue().isInherit()) { if (ekv != null || !value.isInherit()) {
break; break;
} }
case CURRENT_TENANT: case CURRENT_TENANT:
ekv = dynamicPredicateValueCtx.getTenantValue(value.getDynamicValue().getSourceAttribute()); ekv = dynamicPredicateValueCtx.getTenantValue(value.getSourceAttribute());
} }
} }
return ekv; return ekv;

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java

@ -80,7 +80,7 @@ class AlarmState {
public boolean process(TbContext ctx, long ts) throws ExecutionException, InterruptedException { public boolean process(TbContext ctx, long ts) throws ExecutionException, InterruptedException {
initCurrentAlarm(ctx); initCurrentAlarm(ctx);
return createOrClearAlarms(ctx, null, ts, null, AlarmRuleState::eval); return createOrClearAlarms(ctx, null, ts, null, (alarmState, tsParam) -> alarmState.eval(tsParam, dataSnapshot));
} }
public <T> boolean createOrClearAlarms(TbContext ctx, TbMsg msg, T data, SnapshotUpdate update, BiFunction<AlarmRuleState, T, AlarmEvalResult> evalFunction) { public <T> boolean createOrClearAlarms(TbContext ctx, TbMsg msg, T data, SnapshotUpdate update, BiFunction<AlarmRuleState, T, AlarmEvalResult> evalFunction) {

44
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/ProfileState.java

@ -19,24 +19,21 @@ import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter;
import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey;
import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.AlarmConditionSpecType;
import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.query.ComplexFilterPredicate; import org.thingsboard.server.common.data.query.ComplexFilterPredicate;
import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValue;
import org.thingsboard.server.common.data.query.DynamicValueSourceType; import org.thingsboard.server.common.data.query.DynamicValueSourceType;
import org.thingsboard.server.common.data.query.EntityKey;
import org.thingsboard.server.common.data.query.EntityKeyType;
import org.thingsboard.server.common.data.query.FilterPredicateValue;
import org.thingsboard.server.common.data.query.KeyFilter;
import org.thingsboard.server.common.data.query.KeyFilterPredicate; import org.thingsboard.server.common.data.query.KeyFilterPredicate;
import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate; import org.thingsboard.server.common.data.query.SimpleKeyFilterPredicate;
import org.thingsboard.server.common.data.query.StringFilterPredicate;
import javax.print.attribute.standard.Severity;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
@ -79,6 +76,7 @@ class ProfileState {
ruleKeys.add(keyFilter.getKey()); ruleKeys.add(keyFilter.getKey());
addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys); addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, ruleKeys);
} }
addEntityKeysFromAlarmConditionSpec(alarmRule);
})); }));
if (alarm.getClearRule() != null) { if (alarm.getClearRule() != null) {
var clearAlarmKeys = alarmClearKeys.computeIfAbsent(alarm.getId(), id -> new HashSet<>()); var clearAlarmKeys = alarmClearKeys.computeIfAbsent(alarm.getId(), id -> new HashSet<>());
@ -87,11 +85,43 @@ class ProfileState {
clearAlarmKeys.add(keyFilter.getKey()); clearAlarmKeys.add(keyFilter.getKey());
addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, clearAlarmKeys); addDynamicValuesRecursively(keyFilter.getPredicate(), entityKeys, clearAlarmKeys);
} }
addEntityKeysFromAlarmConditionSpec(alarm.getClearRule());
} }
} }
} }
} }
private void addEntityKeysFromAlarmConditionSpec(AlarmRule alarmRule) {
AlarmConditionSpec spec = alarmRule.getCondition().getSpec();
if (spec == null) {
return;
}
AlarmConditionSpecType specType = spec.getType();
switch (specType) {
case DURATION:
DurationAlarmConditionSpec duration = (DurationAlarmConditionSpec) spec;
if(duration.getPredicate().getDynamicValue() != null
&& duration.getPredicate().getDynamicValue().getSourceAttribute() != null) {
entityKeys.add(
new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE,
duration.getPredicate().getDynamicValue().getSourceAttribute())
);
}
break;
case REPEATING:
RepeatingAlarmConditionSpec repeating = (RepeatingAlarmConditionSpec) spec;
if(repeating.getPredicate().getDynamicValue() != null
&& repeating.getPredicate().getDynamicValue().getSourceAttribute() != null) {
entityKeys.add(
new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE,
repeating.getPredicate().getDynamicValue().getSourceAttribute())
);
}
break;
}
}
private void addDynamicValuesRecursively(KeyFilterPredicate predicate, Set<AlarmConditionFilterKey> entityKeys, Set<AlarmConditionFilterKey> ruleKeys) { private void addDynamicValuesRecursively(KeyFilterPredicate predicate, Set<AlarmConditionFilterKey> entityKeys, Set<AlarmConditionFilterKey> ruleKeys) {
switch (predicate.getType()) { switch (predicate.getType()) {
case STRING: case STRING:

8
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java

@ -174,7 +174,13 @@ public class TbHttpClient {
String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg); String endpointUrl = TbNodeUtils.processPattern(config.getRestEndpointUrlPattern(), msg);
HttpHeaders headers = prepareHeaders(msg); HttpHeaders headers = prepareHeaders(msg);
HttpMethod method = HttpMethod.valueOf(config.getRequestMethod()); HttpMethod method = HttpMethod.valueOf(config.getRequestMethod());
HttpEntity<String> entity = new HttpEntity<>(msg.getData(), headers); HttpEntity<String> entity;
if(HttpMethod.GET.equals(method) || HttpMethod.HEAD.equals(method) ||
HttpMethod.OPTIONS.equals(method) || HttpMethod.TRACE.equals(method)) {
entity = new HttpEntity<>(headers);
} else {
entity = new HttpEntity<>(msg.getData(), headers);
}
ListenableFuture<ResponseEntity<String>> future = httpClient.exchange( ListenableFuture<ResponseEntity<String>> future = httpClient.exchange(
endpointUrl, method, entity, String.class); endpointUrl, method, entity, String.class);

649
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/profile/TbDeviceProfileNodeTest.java

@ -42,6 +42,8 @@ import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType;
import org.thingsboard.server.common.data.device.profile.AlarmRule; import org.thingsboard.server.common.data.device.profile.AlarmRule;
import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.DurationAlarmConditionSpec;
import org.thingsboard.server.common.data.device.profile.RepeatingAlarmConditionSpec;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.DeviceProfileId;
@ -64,12 +66,15 @@ import org.thingsboard.server.dao.model.sql.AttributeKvCompositeKey;
import org.thingsboard.server.dao.model.sql.AttributeKvEntity; import org.thingsboard.server.dao.model.sql.AttributeKvEntity;
import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.timeseries.TimeseriesService;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.TreeMap; import java.util.TreeMap;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@ -94,10 +99,10 @@ public class TbDeviceProfileNodeTest {
@Mock @Mock
private AttributesService attributesService; private AttributesService attributesService;
private TenantId tenantId = new TenantId(UUID.randomUUID()); private final TenantId tenantId = new TenantId(UUID.randomUUID());
private DeviceId deviceId = new DeviceId(UUID.randomUUID()); private final DeviceId deviceId = new DeviceId(UUID.randomUUID());
private CustomerId customerId = new CustomerId(UUID.randomUUID()); private final CustomerId customerId = new CustomerId(UUID.randomUUID());
private DeviceProfileId deviceProfileId = new DeviceProfileId(UUID.randomUUID()); private final DeviceProfileId deviceProfileId = new DeviceProfileId(UUID.randomUUID());
@Test @Test
public void testRandomMessageType() throws Exception { public void testRandomMessageType() throws Exception {
@ -444,6 +449,642 @@ public class TbDeviceProfileNodeTest {
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any()); verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
} }
@Test
public void testCurrentDeviceAttributeForDynamicDurationValue() throws Exception {
init();
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
DeviceProfileData deviceProfileData = new DeviceProfileData();
AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute"
);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setLongValue(30L);
attributeKvEntity.setLastUpdateTs(0L);
AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay"
);
AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity();
alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey);
long alarmDelayInSeconds = 5L;
alarmDelayAttributeKvEntity.setLongValue(alarmDelayInSeconds);
alarmDelayAttributeKvEntity.setLastUpdateTs(0L);
AttributeKvEntry entry = attributeKvEntity.toData();
AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData();
ListenableFuture<List<AttributeKvEntry>> listListenableFuture =
Futures.immediateFuture(Arrays.asList(entry, alarmDelayAttributeKvEntry));
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false)
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
FilterPredicateValue<Long> filterPredicateValue = new FilterPredicateValue<>(
10L,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", false)
);
DurationAlarmConditionSpec durationSpec = new DurationAlarmConditionSpec();
durationSpec.setUnit(TimeUnit.SECONDS);
durationSpec.setPredicate(filterPredicateValue);
alarmCondition.setSpec(durationSpec);
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm");
dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)));
deviceProfileData.setAlarms(Collections.singletonList(dpa));
deviceProfile.setProfileData(deviceProfileData);
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFuture);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 35);
TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg);
verify(ctx).tellSuccess(msg);
int halfOfAlarmDelay = new BigDecimal(alarmDelayInSeconds)
.multiply(BigDecimal.valueOf(1000))
.divide(BigDecimal.valueOf(2), 3, RoundingMode.HALF_EVEN)
.intValueExact();
Thread.sleep(halfOfAlarmDelay);
verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created");
Thread.sleep(halfOfAlarmDelay);
TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg2);
verify(ctx).tellSuccess(msg2);
verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testInheritTenantAttributeForDuration() throws Exception {
init();
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
DeviceProfileData deviceProfileData = new DeviceProfileData();
Device device = new Device();
device.setId(deviceId);
device.setCustomerId(customerId);
AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute"
);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setLongValue(30L);
attributeKvEntity.setLastUpdateTs(0L);
AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay"
);
AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity();
alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey);
long alarmDelayInSeconds = 5L;
alarmDelayAttributeKvEntity.setLongValue(alarmDelayInSeconds);
alarmDelayAttributeKvEntity.setLastUpdateTs(0L);
AttributeKvEntry entry = attributeKvEntity.toData();
AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData();
ListenableFuture<Optional<AttributeKvEntry>> optionalDurationAttribute =
Futures.immediateFuture(Optional.of(alarmDelayAttributeKvEntry));
ListenableFuture<List<AttributeKvEntry>> listNoDurationAttribute =
Futures.immediateFuture(Collections.singletonList(entry));
ListenableFuture<Optional<AttributeKvEntry>> emptyOptional =
Futures.immediateFuture(Optional.empty());
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false)
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
FilterPredicateValue<Long> filterPredicateValue = new FilterPredicateValue<>(
10L,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", true)
);
DurationAlarmConditionSpec durationSpec = new DurationAlarmConditionSpec();
durationSpec.setUnit(TimeUnit.SECONDS);
durationSpec.setPredicate(filterPredicateValue);
alarmCondition.setSpec(durationSpec);
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm");
dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)));
deviceProfileData.setAlarms(Collections.singletonList(dpa));
deviceProfile.setProfileData(deviceProfileData);
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString()))
.thenReturn(optionalDurationAttribute);
Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId))
.thenReturn(device);
Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString()))
.thenReturn(emptyOptional);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listNoDurationAttribute);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 150);
TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg);
verify(ctx).tellSuccess(msg);
int halfOfAlarmDelay = new BigDecimal(alarmDelayInSeconds)
.multiply(BigDecimal.valueOf(1000))
.divide(BigDecimal.valueOf(2), 3, RoundingMode.HALF_EVEN)
.intValueExact();
Thread.sleep(halfOfAlarmDelay);
verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created");
Thread.sleep(halfOfAlarmDelay);
TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg2);
verify(ctx).tellSuccess(msg2);
verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testCurrentDeviceAttributeForDynamicRepeatingValue() throws Exception {
init();
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
DeviceProfileData deviceProfileData = new DeviceProfileData();
AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute"
);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setLongValue(30L);
attributeKvEntity.setLastUpdateTs(0L);
AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay"
);
AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity();
alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey);
long alarmRepeating = 2;
alarmDelayAttributeKvEntity.setLongValue(alarmRepeating);
alarmDelayAttributeKvEntity.setLastUpdateTs(0L);
AttributeKvEntry entry = attributeKvEntity.toData();
AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData();
ListenableFuture<List<AttributeKvEntry>> listListenableFuture =
Futures.immediateFuture(Arrays.asList(entry, alarmDelayAttributeKvEntry));
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false)
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
FilterPredicateValue<Integer> filterPredicateValue = new FilterPredicateValue<>(
10,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", false)
);
RepeatingAlarmConditionSpec repeatingSpec = new RepeatingAlarmConditionSpec();
repeatingSpec.setPredicate(filterPredicateValue);
alarmCondition.setSpec(repeatingSpec);
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm");
dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)));
deviceProfileData.setAlarms(Collections.singletonList(dpa));
deviceProfile.setProfileData(deviceProfileData);
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFuture);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 150);
TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg);
verify(ctx).tellSuccess(msg);
verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created");
TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg2);
verify(ctx).tellSuccess(msg2);
verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testInheritTenantAttributeForRepeating() throws Exception {
init();
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
DeviceProfileData deviceProfileData = new DeviceProfileData();
AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute"
);
Device device = new Device();
device.setId(deviceId);
device.setCustomerId(customerId);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setLongValue(30L);
attributeKvEntity.setLastUpdateTs(0L);
AttributeKvCompositeKey alarmDelayCompositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "alarm_delay"
);
AttributeKvEntity alarmDelayAttributeKvEntity = new AttributeKvEntity();
alarmDelayAttributeKvEntity.setId(alarmDelayCompositeKey);
long repeatingCondition = 2;
alarmDelayAttributeKvEntity.setLongValue(repeatingCondition);
alarmDelayAttributeKvEntity.setLastUpdateTs(0L);
AttributeKvEntry entry = attributeKvEntity.toData();
AttributeKvEntry alarmDelayAttributeKvEntry = alarmDelayAttributeKvEntity.toData();
ListenableFuture<Optional<AttributeKvEntry>> optionalDurationAttribute =
Futures.immediateFuture(Optional.of(alarmDelayAttributeKvEntry));
ListenableFuture<List<AttributeKvEntry>> listNoDurationAttribute =
Futures.immediateFuture(Collections.singletonList(entry));
ListenableFuture<Optional<AttributeKvEntry>> emptyOptional =
Futures.immediateFuture(Optional.empty());
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute", false)
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
FilterPredicateValue<Integer> filterPredicateValue = new FilterPredicateValue<>(
10,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_delay", true)
);
RepeatingAlarmConditionSpec repeatingSpec = new RepeatingAlarmConditionSpec();
repeatingSpec.setPredicate(filterPredicateValue);
alarmCondition.setSpec(repeatingSpec);
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm");
dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)));
deviceProfileData.setAlarms(Collections.singletonList(dpa));
deviceProfile.setProfileData(deviceProfileData);
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(tenantId), Mockito.anyString(), Mockito.anyString()))
.thenReturn(optionalDurationAttribute);
Mockito.when(ctx.getDeviceService().findDeviceById(tenantId, deviceId))
.thenReturn(device);
Mockito.when(attributesService.find(eq(tenantId), eq(customerId), eq(DataConstants.SERVER_SCOPE), Mockito.anyString()))
.thenReturn(emptyOptional);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listNoDurationAttribute);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 150);
TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg);
verify(ctx).tellSuccess(msg);
verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created");
TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg2);
verify(ctx).tellSuccess(msg2);
verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testCurrentDeviceAttributeForUseDefaultDurationWhenDynamicDurationValueIsNull() throws Exception {
init();
long alarmDelayInSeconds = 5;
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
DeviceProfileData deviceProfileData = new DeviceProfileData();
Device device = new Device();
device.setId(deviceId);
device.setCustomerId(customerId);
AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute"
);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setLongValue(30L);
attributeKvEntity.setLastUpdateTs(0L);
AttributeKvEntry entry = attributeKvEntity.toData();
ListenableFuture<List<AttributeKvEntry>> listListenableFuture =
Futures.immediateFuture(Collections.singletonList(entry));
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute")
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
FilterPredicateValue<Long> filterPredicateValue = new FilterPredicateValue<>(
alarmDelayInSeconds,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, null, false)
);
DurationAlarmConditionSpec durationSpec = new DurationAlarmConditionSpec();
durationSpec.setUnit(TimeUnit.SECONDS);
durationSpec.setPredicate(filterPredicateValue);
alarmCondition.setSpec(durationSpec);
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm");
dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)));
deviceProfileData.setAlarms(Collections.singletonList(dpa));
deviceProfile.setProfileData(deviceProfileData);
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFuture);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 35);
TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg);
verify(ctx).tellSuccess(msg);
int halfOfAlarmDelay = new BigDecimal(alarmDelayInSeconds)
.multiply(BigDecimal.valueOf(1000))
.divide(BigDecimal.valueOf(2), 3, RoundingMode.HALF_EVEN)
.intValueExact();
Thread.sleep(halfOfAlarmDelay);
verify(ctx, Mockito.never()).tellNext(theMsg, "Alarm Created");
Thread.sleep(halfOfAlarmDelay);
TbMsg msg2 = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg2);
verify(ctx).tellSuccess(msg2);
verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test
public void testCurrentDeviceAttributeForUseDefaultRepeatingWhenDynamicDurationValueIsNull() throws Exception {
init();
DeviceProfile deviceProfile = new DeviceProfile();
deviceProfile.setId(deviceProfileId);
DeviceProfileData deviceProfileData = new DeviceProfileData();
Device device = new Device();
device.setId(deviceId);
device.setCustomerId(customerId);
AttributeKvCompositeKey compositeKey = new AttributeKvCompositeKey(
EntityType.TENANT, deviceId.getId(), "SERVER_SCOPE", "greaterAttribute"
);
AttributeKvEntity attributeKvEntity = new AttributeKvEntity();
attributeKvEntity.setId(compositeKey);
attributeKvEntity.setLongValue(30L);
attributeKvEntity.setLastUpdateTs(0L);
AttributeKvEntry entry = attributeKvEntity.toData();
ListenableFuture<List<AttributeKvEntry>> listListenableFuture =
Futures.immediateFuture(Collections.singletonList(entry));
AlarmConditionFilter highTempFilter = new AlarmConditionFilter();
highTempFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.TIME_SERIES, "temperature"));
highTempFilter.setValueType(EntityKeyValueType.NUMERIC);
NumericFilterPredicate highTemperaturePredicate = new NumericFilterPredicate();
highTemperaturePredicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER);
highTemperaturePredicate.setValue(new FilterPredicateValue<>(
0.0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "greaterAttribute")
));
highTempFilter.setPredicate(highTemperaturePredicate);
AlarmCondition alarmCondition = new AlarmCondition();
alarmCondition.setCondition(Collections.singletonList(highTempFilter));
RepeatingAlarmConditionSpec repeating = new RepeatingAlarmConditionSpec();
repeating.setPredicate(new FilterPredicateValue<>(
0,
null,
new DynamicValue<>(DynamicValueSourceType.CURRENT_DEVICE, "alarm_rule", false)
));
alarmCondition.setSpec(repeating);
AlarmRule alarmRule = new AlarmRule();
alarmRule.setCondition(alarmCondition);
DeviceProfileAlarm dpa = new DeviceProfileAlarm();
dpa.setId("highTemperatureAlarmID");
dpa.setAlarmType("highTemperatureAlarm");
dpa.setCreateRules(new TreeMap<>(Collections.singletonMap(AlarmSeverity.CRITICAL, alarmRule)));
deviceProfileData.setAlarms(Collections.singletonList(dpa));
deviceProfile.setProfileData(deviceProfileData);
Mockito.when(cache.get(tenantId, deviceId)).thenReturn(deviceProfile);
Mockito.when(timeseriesService.findLatest(tenantId, deviceId, Collections.singleton("temperature")))
.thenReturn(Futures.immediateFuture(Collections.emptyList()));
Mockito.when(alarmService.findLatestByOriginatorAndType(tenantId, deviceId, "highTemperatureAlarm"))
.thenReturn(Futures.immediateFuture(null));
Mockito.when(alarmService.createOrUpdateAlarm(Mockito.any())).thenAnswer(AdditionalAnswers.returnsFirstArg());
Mockito.when(ctx.getAttributesService()).thenReturn(attributesService);
Mockito.when(attributesService.find(eq(tenantId), eq(deviceId), Mockito.anyString(), Mockito.anySet()))
.thenReturn(listListenableFuture);
TbMsg theMsg = TbMsg.newMsg("ALARM", deviceId, new TbMsgMetaData(), "");
Mockito.when(ctx.newMsg(Mockito.anyString(), Mockito.anyString(), Mockito.any(), Mockito.any(), Mockito.any(), Mockito.anyString()))
.thenReturn(theMsg);
ObjectNode data = mapper.createObjectNode();
data.put("temperature", 35);
TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, new TbMsgMetaData(),
TbMsgDataType.JSON, mapper.writeValueAsString(data), null, null);
node.onMsg(ctx, msg);
verify(ctx).tellSuccess(msg);
verify(ctx).tellNext(theMsg, "Alarm Created");
verify(ctx, Mockito.never()).tellFailure(Mockito.any(), Mockito.any());
}
@Test @Test
public void testCurrentCustomersAttributeForDynamicValue() throws Exception { public void testCurrentCustomersAttributeForDynamicValue() throws Exception {
init(); init();

23
ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.ts

@ -30,7 +30,9 @@ import {
DynamicValueSourceType, DynamicValueSourceType,
dynamicValueSourceTypeTranslationMap, dynamicValueSourceTypeTranslationMap,
EntityKeyValueType, EntityKeyValueType,
FilterPredicateValue FilterPredicateValue,
getDynamicSourcesForAllowUser,
inheritModeForDynamicValueSourceType
} from '@shared/models/query/query.models'; } from '@shared/models/query/query.models';
@Component({ @Component({
@ -52,22 +54,14 @@ import {
}) })
export class FilterPredicateValueComponent implements ControlValueAccessor, Validator, OnInit { export class FilterPredicateValueComponent implements ControlValueAccessor, Validator, OnInit {
private readonly inheritModeForSources: DynamicValueSourceType[] = [ private readonly inheritModeForSources: DynamicValueSourceType[] = inheritModeForDynamicValueSourceType;
DynamicValueSourceType.CURRENT_CUSTOMER,
DynamicValueSourceType.CURRENT_DEVICE];
@Input() disabled: boolean; @Input() disabled: boolean;
@Input() @Input()
set allowUserDynamicSource(allow: boolean) { set allowUserDynamicSource(allow: boolean) {
this.dynamicValueSourceTypes = [DynamicValueSourceType.CURRENT_TENANT, this.dynamicValueSourceTypes = getDynamicSourcesForAllowUser(allow);
DynamicValueSourceType.CURRENT_CUSTOMER];
this.allow = allow; this.allow = allow;
if (allow) {
this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_USER);
} else {
this.dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_DEVICE);
}
} }
private onlyUserDynamicSourceValue = false; private onlyUserDynamicSourceValue = false;
@ -92,8 +86,9 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, Vali
valueTypeEnum = EntityKeyValueType; valueTypeEnum = EntityKeyValueType;
dynamicValueSourceTypes: DynamicValueSourceType[] = [DynamicValueSourceType.CURRENT_TENANT, allow = true;
DynamicValueSourceType.CURRENT_CUSTOMER, DynamicValueSourceType.CURRENT_USER];
dynamicValueSourceTypes: DynamicValueSourceType[] = getDynamicSourcesForAllowUser(this.allow);
dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap; dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap;
@ -103,8 +98,6 @@ export class FilterPredicateValueComponent implements ControlValueAccessor, Vali
inheritMode = false; inheritMode = false;
allow = true;
private propagateChange = null; private propagateChange = null;
private propagateChangePending = false; private propagateChangePending = false;

3
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -136,6 +136,7 @@ import { EMBED_DASHBOARD_DIALOG_TOKEN } from '@home/components/widget/dialog/emb
import { EdgeDownlinkTableComponent } from '@home/components/edge/edge-downlink-table.component'; import { EdgeDownlinkTableComponent } from '@home/components/edge/edge-downlink-table.component';
import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-downlink-table-header.component'; import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-downlink-table-header.component';
import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component'; import { DisplayWidgetTypesPanelComponent } from '@home/components/dashboard-page/widget-types-panel.component';
import { AlarmDurationPredicateValueComponent } from '@home/components/profile/alarm/alarm-duration-predicate-value.component';
import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component'; import { DashboardImageDialogComponent } from '@home/components/dashboard-page/dashboard-image-dialog.component';
import { WidgetContainerComponent } from '@home/components/widget/widget-container.component'; import { WidgetContainerComponent } from '@home/components/widget/widget-container.component';
import { SnmpDeviceProfileTransportModule } from '@home/components/profile/device/snpm/snmp-device-profile-transport.module'; import { SnmpDeviceProfileTransportModule } from '@home/components/profile/device/snpm/snmp-device-profile-transport.module';
@ -239,6 +240,7 @@ import { DeviceCredentialsModule } from '@home/components/device/device-credenti
AlarmScheduleInfoComponent, AlarmScheduleInfoComponent,
DeviceProfileProvisionConfigurationComponent, DeviceProfileProvisionConfigurationComponent,
AlarmScheduleComponent, AlarmScheduleComponent,
AlarmDurationPredicateValueComponent,
DeviceWizardDialogComponent, DeviceWizardDialogComponent,
AlarmScheduleDialogComponent, AlarmScheduleDialogComponent,
EditAlarmDetailsDialogComponent, EditAlarmDetailsDialogComponent,
@ -348,6 +350,7 @@ import { DeviceCredentialsModule } from '@home/components/device/device-credenti
AlarmScheduleInfoComponent, AlarmScheduleInfoComponent,
AlarmScheduleComponent, AlarmScheduleComponent,
AlarmScheduleDialogComponent, AlarmScheduleDialogComponent,
AlarmDurationPredicateValueComponent,
EditAlarmDetailsDialogComponent, EditAlarmDetailsDialogComponent,
DeviceProfileProvisionConfigurationComponent, DeviceProfileProvisionConfigurationComponent,
AlarmScheduleComponent, AlarmScheduleComponent,

79
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.html

@ -0,0 +1,79 @@
<!--
Copyright © 2016-2021 The Thingsboard Authors
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
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,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<div fxFlex fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px" [formGroup]="alarmDurationPredicateValueFormGroup">
<div fxFlex fxLayout="column" [fxShow]="!dynamicMode">
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
<mat-label></mat-label>
<input required type="number" matInput
step="1" min="1" max="2147483647"
formControlName="defaultValue"
placeholder="{{ defaultValuePlaceholder | translate }}">
<mat-error *ngIf="alarmDurationPredicateValueFormGroup.get('defaultValue').hasError('required')">
{{ defaultValueRequiredError | translate }}
</mat-error>
<mat-error *ngIf="alarmDurationPredicateValueFormGroup.get('defaultValue').hasError('min')">
{{ defaultValueRangeError | translate }}
</mat-error>
<mat-error *ngIf="alarmDurationPredicateValueFormGroup.get('defaultValue').hasError('max')">
{{ defaultValueRangeError | translate }}
</mat-error>
<mat-error *ngIf="alarmDurationPredicateValueFormGroup.get('defaultValue').hasError('pattern')">
{{ defaultValuePatternError | translate }}
</mat-error>
</mat-form-field>
</div>
<div fxFlex fxLayout="column" [fxShow]="dynamicMode">
<div formGroupName="dynamicValue" fxLayout="row" fxLayoutAlign="start center" fxLayoutGap="8px">
<div fxFlex="40" fxLayout="column">
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block">
<mat-label></mat-label>
<mat-select formControlName="sourceType" placeholder="{{'filter.dynamic-source-type' | translate}}">
<mat-option [value]="null">
{{'filter.no-dynamic-value' | translate}}
</mat-option>
<mat-option *ngFor="let sourceType of dynamicValueSourceTypes" [value]="sourceType">
{{dynamicValueSourceTypeTranslations.get(sourceType) | translate}}
</mat-option>
</mat-select>
</mat-form-field>
</div>
<div fxFlex fxLayout="column">
<mat-form-field floatLabel="always" hideRequiredMarker class="mat-block source-attribute">
<mat-label></mat-label>
<input matInput formControlName="sourceAttribute" placeholder="{{'filter.source-attribute' | translate}}">
</mat-form-field>
</div>
<div *ngIf="inheritMode"
fxLayout="column"
style="padding-top: 6px">
<mat-checkbox formControlName="inherit">
{{ 'filter.inherit-owner' | translate}}
</mat-checkbox>
</div>
</div>
</div>
<button mat-icon-button
class="mat-elevation-z1 tb-mat-32"
color="primary"
type="button"
matTooltip="{{ (dynamicMode ? 'filter.switch-to-default-value' : 'filter.switch-to-dynamic-value') | translate }}"
matTooltipPosition="above"
(click)="dynamicMode = !dynamicMode">
<mat-icon class="tb-mat-20" [svgIcon]="dynamicMode ? 'mdi:numeric' : 'mdi:variable'"></mat-icon>
</button>
</div>

23
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.scss

@ -0,0 +1,23 @@
/**
* Copyright © 2016-2021 The Thingsboard Authors
*
* 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
*
* 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,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
:host ::ng-deep {
.source-attribute {
.mat-form-field-infix{
width: 100%;
}
}
}

153
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-duration-predicate-value.component.ts

@ -0,0 +1,153 @@
///
/// Copyright © 2016-2021 The Thingsboard Authors
///
/// 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
///
/// 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,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms';
import {
DynamicValueSourceType,
dynamicValueSourceTypeTranslationMap,
FilterPredicateValue,
getDynamicSourcesForAllowUser,
inheritModeForDynamicValueSourceType
} from '@shared/models/query/query.models';
import { AlarmConditionType } from '@shared/models/device.models';
@Component({
selector: 'tb-alarm-duration-predicate-value',
templateUrl: './alarm-duration-predicate-value.component.html',
styleUrls: ['./alarm-duration-predicate-value.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => AlarmDurationPredicateValueComponent),
multi: true
}
]
})
export class AlarmDurationPredicateValueComponent implements ControlValueAccessor, OnInit {
private readonly inheritModeForSources = inheritModeForDynamicValueSourceType;
@Input()
set alarmConditionType(alarmConditionType: AlarmConditionType) {
switch (alarmConditionType) {
case AlarmConditionType.REPEATING:
this.defaultValuePlaceholder = 'device-profile.condition-repeating-value-required';
this.defaultValueRequiredError = 'device-profile.condition-repeating-value-range';
this.defaultValueRangeError = 'device-profile.condition-repeating-value-range';
this.defaultValuePatternError = 'device-profile.condition-repeating-value-pattern';
break;
case AlarmConditionType.DURATION:
this.defaultValuePlaceholder = 'device-profile.condition-duration-value';
this.defaultValueRequiredError = 'device-profile.condition-duration-value-required';
this.defaultValueRangeError = 'device-profile.condition-duration-value-range';
this.defaultValuePatternError = 'device-profile.condition-duration-value-pattern';
break;
}
}
defaultValuePlaceholder = '';
defaultValueRequiredError = '';
defaultValueRangeError = '';
defaultValuePatternError = '';
dynamicValueSourceTypes: DynamicValueSourceType[] = getDynamicSourcesForAllowUser(false);
dynamicValueSourceTypeTranslations = dynamicValueSourceTypeTranslationMap;
alarmDurationPredicateValueFormGroup: FormGroup;
dynamicMode = false;
inheritMode = false;
private propagateChange = null;
constructor(private fb: FormBuilder) {
}
ngOnInit(): void {
this.alarmDurationPredicateValueFormGroup = this.fb.group({
defaultValue: [0, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]],
dynamicValue: this.fb.group(
{
sourceType: [null],
sourceAttribute: [null],
inherit: [false]
}
)
});
this.alarmDurationPredicateValueFormGroup.get('dynamicValue').get('sourceType').valueChanges.subscribe(
(sourceType) => {
if (!sourceType) {
this.alarmDurationPredicateValueFormGroup.get('dynamicValue').get('sourceAttribute').patchValue(null, {emitEvent: false});
}
this.updateShowInheritMode(sourceType);
}
);
this.alarmDurationPredicateValueFormGroup.valueChanges.subscribe(() => {
this.updateModel();
});
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void {
}
setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.alarmDurationPredicateValueFormGroup.disable({emitEvent: false});
} else {
this.alarmDurationPredicateValueFormGroup.enable({emitEvent: false});
}
}
writeValue(predicateValue: FilterPredicateValue<string | number | boolean>): void {
this.alarmDurationPredicateValueFormGroup.patchValue({
defaultValue: predicateValue ? predicateValue.defaultValue : null,
dynamicValue: {
sourceType: predicateValue?.dynamicValue ? predicateValue.dynamicValue.sourceType : null,
sourceAttribute: predicateValue?.dynamicValue ? predicateValue.dynamicValue.sourceAttribute : null,
inherit: predicateValue?.dynamicValue ? predicateValue.dynamicValue.inherit : null
}
}, {emitEvent: false});
}
private updateModel() {
let predicateValue: FilterPredicateValue<string | number | boolean> = null;
if (this.alarmDurationPredicateValueFormGroup.valid) {
predicateValue = this.alarmDurationPredicateValueFormGroup.getRawValue();
if (predicateValue.dynamicValue) {
if (!predicateValue.dynamicValue.sourceType || !predicateValue.dynamicValue.sourceAttribute) {
predicateValue.dynamicValue = null;
}
}
}
this.propagateChange(predicateValue);
}
private updateShowInheritMode(sourceType: DynamicValueSourceType) {
if (this.inheritModeForSources.includes(sourceType)) {
this.inheritMode = true;
} else {
this.alarmDurationPredicateValueFormGroup.get('dynamicValue.inherit').patchValue(false, {emitEvent: false});
this.inheritMode = false;
}
}
}

76
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html

@ -37,7 +37,7 @@
[entityId]="entityId" [entityId]="entityId"
formControlName="keyFilters"> formControlName="keyFilters">
</tb-key-filter-list> </tb-key-filter-list>
<section formGroupName="spec" class="row"> <section formGroupName="spec" style="margin-top: 1em">
<mat-form-field class="mat-block" hideRequiredMarker> <mat-form-field class="mat-block" hideRequiredMarker>
<mat-label translate>device-profile.condition-type</mat-label> <mat-label translate>device-profile.condition-type</mat-label>
<mat-select formControlName="type" required> <mat-select formControlName="type" required>
@ -49,60 +49,26 @@
{{ 'device-profile.condition-type-required' | translate }} {{ 'device-profile.condition-type-required' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
<div fxLayout="row" fxLayoutGap="8px" *ngIf="conditionFormGroup.get('spec.type').value == AlarmConditionType.DURATION"> <div fxLayout="row" fxLayout.xs="column" fxLayoutGap.gt-xs="16px" *ngIf="conditionFormGroup.get('spec.type').value != AlarmConditionType.SIMPLE">
<mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always"> <tb-alarm-duration-predicate-value
<mat-label></mat-label> fxLayout="row" fxFlex formControlName="predicate"
<input type="number" required [alarmConditionType]="conditionFormGroup.get('spec.type').value"
step="1" min="1" max="2147483647" matInput >
placeholder="{{ 'device-profile.condition-duration-value' | translate }}" </tb-alarm-duration-predicate-value>
formControlName="value"> <div fxFlex="23" *ngIf="conditionFormGroup.get('spec.type').value == AlarmConditionType.DURATION">
<mat-error *ngIf="conditionFormGroup.get('spec.value').hasError('required')"> <mat-form-field class="mat-block" hideRequiredMarker floatLabel="always">
{{ 'device-profile.condition-duration-value-required' | translate }} <mat-label></mat-label>
</mat-error> <mat-select formControlName="unit" required
<mat-error *ngIf="conditionFormGroup.get('spec.value').hasError('min')"> placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}">
{{ 'device-profile.condition-duration-value-range' | translate }} <mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit">
</mat-error> {{ timeUnitTranslations.get(timeUnit) | translate }}
<mat-error *ngIf="conditionFormGroup.get('spec.value').hasError('max')"> </mat-option>
{{ 'device-profile.condition-duration-value-range' | translate }} </mat-select>
</mat-error> <mat-error *ngIf="conditionFormGroup.get('spec.unit').hasError('required')">
<mat-error *ngIf="conditionFormGroup.get('spec.value').hasError('pattern')"> {{ 'device-profile.condition-duration-time-unit-required' | translate }}
{{ 'device-profile.condition-duration-value-pattern' | translate }} </mat-error>
</mat-error> </mat-form-field>
</mat-form-field> </div>
<mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always">
<mat-label></mat-label>
<mat-select formControlName="unit"
required
placeholder="{{ 'device-profile.condition-duration-time-unit' | translate }}">
<mat-option *ngFor="let timeUnit of timeUnits" [value]="timeUnit">
{{ timeUnitTranslations.get(timeUnit) | translate }}
</mat-option>
</mat-select>
<mat-error *ngIf="conditionFormGroup.get('spec.unit').hasError('required')">
{{ 'device-profile.condition-duration-time-unit-required' | translate }}
</mat-error>
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="8px" *ngIf="conditionFormGroup.get('spec.type').value == AlarmConditionType.REPEATING">
<mat-form-field class="mat-block" hideRequiredMarker fxFlex floatLabel="always">
<mat-label></mat-label>
<input type="number" required
step="1" min="1" max="2147483647" matInput
placeholder="{{ 'device-profile.condition-repeating-value' | translate }}"
formControlName="count">
<mat-error *ngIf="conditionFormGroup.get('spec.count').hasError('required')">
{{ 'device-profile.condition-repeating-value-required' | translate }}
</mat-error>
<mat-error *ngIf="conditionFormGroup.get('spec.count').hasError('min')">
{{ 'device-profile.condition-repeating-value-range' | translate }}
</mat-error>
<mat-error *ngIf="conditionFormGroup.get('spec.count').hasError('max')">
{{ 'device-profile.condition-repeating-value-range' | translate }}
</mat-error>
<mat-error *ngIf="conditionFormGroup.get('spec.count').hasError('pattern')">
{{ 'device-profile.condition-repeating-value-pattern' | translate }}
</mat-error>
</mat-form-field>
</div> </div>
</section> </section>
</div> </div>

31
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts

@ -43,12 +43,11 @@ export interface AlarmRuleConditionDialogData {
export class AlarmRuleConditionDialogComponent extends DialogComponent<AlarmRuleConditionDialogComponent, AlarmCondition> export class AlarmRuleConditionDialogComponent extends DialogComponent<AlarmRuleConditionDialogComponent, AlarmCondition>
implements OnInit, ErrorStateMatcher { implements OnInit, ErrorStateMatcher {
timeUnits = Object.keys(TimeUnit); timeUnits = Object.values(TimeUnit);
timeUnitTranslations = timeUnitTranslationMap; timeUnitTranslations = timeUnitTranslationMap;
alarmConditionTypes = Object.keys(AlarmConditionType); alarmConditionTypes = Object.values(AlarmConditionType);
AlarmConditionType = AlarmConditionType; AlarmConditionType = AlarmConditionType;
alarmConditionTypeTranslation = AlarmConditionTypeTranslationMap; alarmConditionTypeTranslation = AlarmConditionTypeTranslationMap;
readonly = this.data.readonly; readonly = this.data.readonly;
condition = this.data.condition; condition = this.data.condition;
entityId = this.data.entityId; entityId = this.data.entityId;
@ -70,9 +69,8 @@ export class AlarmRuleConditionDialogComponent extends DialogComponent<AlarmRule
keyFilters: [keyFiltersToKeyFilterInfos(this.condition?.condition), Validators.required], keyFilters: [keyFiltersToKeyFilterInfos(this.condition?.condition), Validators.required],
spec: this.fb.group({ spec: this.fb.group({
type: [AlarmConditionType.SIMPLE, Validators.required], type: [AlarmConditionType.SIMPLE, Validators.required],
unit: [{value: null, disable: true}, Validators.required], unit: [null, Validators.required],
value: [{value: null, disable: true}, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]], predicate: [null, Validators.required]
count: [{value: null, disable: true}, [Validators.required, Validators.min(1), Validators.max(2147483647), Validators.pattern('[0-9]*')]]
}) })
}); });
this.conditionFormGroup.patchValue({spec: this.condition?.spec}); this.conditionFormGroup.patchValue({spec: this.condition?.spec});
@ -98,42 +96,37 @@ export class AlarmRuleConditionDialogComponent extends DialogComponent<AlarmRule
private updateValidators(type: AlarmConditionType, resetDuration = false, emitEvent = false) { private updateValidators(type: AlarmConditionType, resetDuration = false, emitEvent = false) {
switch (type) { switch (type) {
case AlarmConditionType.DURATION: case AlarmConditionType.DURATION:
this.conditionFormGroup.get('spec.value').enable();
this.conditionFormGroup.get('spec.unit').enable(); this.conditionFormGroup.get('spec.unit').enable();
this.conditionFormGroup.get('spec.count').disable(); this.conditionFormGroup.get('spec.predicate').enable();
if (resetDuration) { if (resetDuration) {
this.conditionFormGroup.get('spec').patchValue({ this.conditionFormGroup.get('spec').patchValue({
count: null predicate: null
}); });
} }
break; break;
case AlarmConditionType.REPEATING: case AlarmConditionType.REPEATING:
this.conditionFormGroup.get('spec.count').enable(); this.conditionFormGroup.get('spec.predicate').enable();
this.conditionFormGroup.get('spec.value').disable();
this.conditionFormGroup.get('spec.unit').disable(); this.conditionFormGroup.get('spec.unit').disable();
if (resetDuration) { if (resetDuration) {
this.conditionFormGroup.get('spec').patchValue({ this.conditionFormGroup.get('spec').patchValue({
value: null, unit: null,
unit: null predicate: null
}); });
} }
break; break;
case AlarmConditionType.SIMPLE: case AlarmConditionType.SIMPLE:
this.conditionFormGroup.get('spec.value').disable();
this.conditionFormGroup.get('spec.unit').disable(); this.conditionFormGroup.get('spec.unit').disable();
this.conditionFormGroup.get('spec.count').disable(); this.conditionFormGroup.get('spec.predicate').disable();
if (resetDuration) { if (resetDuration) {
this.conditionFormGroup.get('spec').patchValue({ this.conditionFormGroup.get('spec').patchValue({
value: null,
unit: null, unit: null,
count: null predicate: null
}); });
} }
break; break;
} }
this.conditionFormGroup.get('spec.value').updateValueAndValidity({emitEvent}); this.conditionFormGroup.get('spec.predicate').updateValueAndValidity({emitEvent});
this.conditionFormGroup.get('spec.unit').updateValueAndValidity({emitEvent}); this.conditionFormGroup.get('spec.unit').updateValueAndValidity({emitEvent});
this.conditionFormGroup.get('spec.count').updateValueAndValidity({emitEvent});
} }
cancel(): void { cancel(): void {

42
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts

@ -18,22 +18,25 @@ import { Component, forwardRef, Input, OnInit } from '@angular/core';
import { import {
ControlValueAccessor, ControlValueAccessor,
FormBuilder, FormBuilder,
FormControl, FormGroup, FormControl,
FormGroup,
NG_VALIDATORS, NG_VALIDATORS,
NG_VALUE_ACCESSOR, NG_VALUE_ACCESSOR,
Validator, Validators Validator,
Validators
} from '@angular/forms'; } from '@angular/forms';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
import { deepClone, isUndefined } from '@core/utils'; import { deepClone, isUndefined } from '@core/utils';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { AlarmCondition, AlarmConditionSpec, AlarmConditionType } from '@shared/models/device.models'; import { AlarmCondition, AlarmConditionType } from '@shared/models/device.models';
import { import {
AlarmRuleConditionDialogComponent, AlarmRuleConditionDialogComponent,
AlarmRuleConditionDialogData AlarmRuleConditionDialogData
} from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component';
import { TimeUnit } from '@shared/models/time/time.models'; import { TimeUnit } from '@shared/models/time/time.models';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { dynamicValueSourceTypeTranslationMap } from '@shared/models/query/query.models';
@Component({ @Component({
selector: 'tb-alarm-rule-condition', selector: 'tb-alarm-rule-condition',
@ -159,22 +162,43 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit
let duringText = ''; let duringText = '';
switch (spec.unit) { switch (spec.unit) {
case TimeUnit.SECONDS: case TimeUnit.SECONDS:
duringText = this.translate.instant('timewindow.seconds', {seconds: spec.value}); duringText = this.translate.instant('timewindow.seconds', {seconds: spec.predicate.defaultValue});
break; break;
case TimeUnit.MINUTES: case TimeUnit.MINUTES:
duringText = this.translate.instant('timewindow.minutes', {minutes: spec.value}); duringText = this.translate.instant('timewindow.minutes', {minutes: spec.predicate.defaultValue});
break; break;
case TimeUnit.HOURS: case TimeUnit.HOURS:
duringText = this.translate.instant('timewindow.hours', {hours: spec.value}); duringText = this.translate.instant('timewindow.hours', {hours: spec.predicate.defaultValue});
break; break;
case TimeUnit.DAYS: case TimeUnit.DAYS:
duringText = this.translate.instant('timewindow.days', {days: spec.value}); duringText = this.translate.instant('timewindow.days', {days: spec.predicate.defaultValue});
break; break;
} }
this.specText = this.translate.instant('device-profile.condition-during', {during: duringText}); if (spec.predicate.dynamicValue && spec.predicate.dynamicValue.sourceAttribute) {
const attributeSource =
this.translate.instant(dynamicValueSourceTypeTranslationMap.get(spec.predicate.dynamicValue.sourceType));
this.specText = this.translate.instant('device-profile.condition-during-dynamic', {
during: duringText,
attribute: `${attributeSource}.${spec.predicate.dynamicValue.sourceAttribute}`
});
} else {
this.specText = this.translate.instant('device-profile.condition-during', {
during: duringText
});
}
break; break;
case AlarmConditionType.REPEATING: case AlarmConditionType.REPEATING:
this.specText = this.translate.instant('device-profile.condition-repeat-times', {count: spec.count}); if (spec.predicate.dynamicValue && spec.predicate.dynamicValue.sourceAttribute) {
const attributeSource =
this.translate.instant(dynamicValueSourceTypeTranslationMap.get(spec.predicate.dynamicValue.sourceType));
this.specText = this.translate.instant('device-profile.condition-repeat-times-dynamic', {
count: spec.predicate.defaultValue,
attribute: `${attributeSource}.${spec.predicate.dynamicValue.sourceAttribute}`
});
} else {
this.specText = this.translate.instant('device-profile.condition-repeat-times',
{count: spec.predicate.defaultValue});
}
break; break;
} }
} }

5
ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.html

@ -15,7 +15,8 @@
limitations under the License. limitations under the License.
--> -->
<div class="tb-request-password-reset-content mat-app-background tb-dark" fxLayout="row" fxLayoutAlign="center center" style="width: 100%;"> <div class="tb-request-password-reset-content mat-app-background tb-dark" fxLayout="row" fxLayoutAlign="center center"
style="width: 100%;">
<mat-card fxFlex="initial" class="tb-request-password-reset-card"> <mat-card fxFlex="initial" class="tb-request-password-reset-card">
<mat-card-title class="layout-padding"> <mat-card-title class="layout-padding">
<span translate class="mat-headline">login.request-password-reset</span> <span translate class="mat-headline">login.request-password-reset</span>
@ -38,7 +39,7 @@
</mat-form-field> </mat-form-field>
<div fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap="16px" fxLayoutAlign="start center" <div fxLayout="column" fxLayout.gt-xs="row" fxLayoutGap="16px" fxLayoutAlign="start center"
fxLayoutAlign.gt-xs="center start"> fxLayoutAlign.gt-xs="center start">
<button mat-raised-button color="accent" type="submit" [disabled]="(isLoading$ | async)"> <button mat-raised-button color="accent" type="submit" [disabled]="(isLoading$ | async) || this.clicked">
{{ 'login.request-password-reset' | translate }} {{ 'login.request-password-reset' | translate }}
</button> </button>
<button mat-raised-button color="primary" type="button" [disabled]="(isLoading$ | async)" <button mat-raised-button color="primary" type="button" [disabled]="(isLoading$ | async)"

8
ui-ngx/src/app/modules/login/pages/login/reset-password-request.component.ts

@ -30,6 +30,8 @@ import { TranslateService } from '@ngx-translate/core';
}) })
export class ResetPasswordRequestComponent extends PageComponent implements OnInit { export class ResetPasswordRequestComponent extends PageComponent implements OnInit {
clicked: boolean = false;
requestPasswordRequest = this.fb.group({ requestPasswordRequest = this.fb.group({
email: ['', [Validators.email, Validators.required]] email: ['', [Validators.email, Validators.required]]
}, {updateOn: 'submit'}); }, {updateOn: 'submit'});
@ -44,8 +46,14 @@ export class ResetPasswordRequestComponent extends PageComponent implements OnIn
ngOnInit() { ngOnInit() {
} }
disableInputs() {
this.requestPasswordRequest.disable();
this.clicked = true;
}
sendResetPasswordLink() { sendResetPasswordLink() {
if (this.requestPasswordRequest.valid) { if (this.requestPasswordRequest.valid) {
this.disableInputs();
this.authService.sendResetPasswordLink(this.requestPasswordRequest.get('email').value).subscribe( this.authService.sendResetPasswordLink(this.requestPasswordRequest.get('email').value).subscribe(
() => { () => {
this.store.dispatch(new ActionNotificationShow({ this.store.dispatch(new ActionNotificationShow({

5
ui-ngx/src/app/shared/models/device.models.ts

@ -23,7 +23,7 @@ import { EntitySearchQuery } from '@shared/models/relation.models';
import { DeviceProfileId } from '@shared/models/id/device-profile-id'; import { DeviceProfileId } from '@shared/models/id/device-profile-id';
import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { RuleChainId } from '@shared/models/id/rule-chain-id';
import { EntityInfoData } from '@shared/models/entity.models'; import { EntityInfoData } from '@shared/models/entity.models';
import { KeyFilter } from '@shared/models/query/query.models'; import { FilterPredicateValue, KeyFilter } from '@shared/models/query/query.models';
import { TimeUnit } from '@shared/models/time/time.models'; import { TimeUnit } from '@shared/models/time/time.models';
import * as _moment from 'moment'; import * as _moment from 'moment';
import { AbstractControl, ValidationErrors } from '@angular/forms'; import { AbstractControl, ValidationErrors } from '@angular/forms';
@ -424,8 +424,7 @@ export const AlarmConditionTypeTranslationMap = new Map<AlarmConditionType, stri
export interface AlarmConditionSpec{ export interface AlarmConditionSpec{
type?: AlarmConditionType; type?: AlarmConditionType;
unit?: TimeUnit; unit?: TimeUnit;
value?: number; predicate: FilterPredicateValue<number>;
count?: number;
} }
export interface AlarmCondition { export interface AlarmCondition {

15
ui-ngx/src/app/shared/models/query/query.models.ts

@ -202,6 +202,17 @@ export function createDefaultFilterPredicate(valueType: EntityKeyValueType, comp
return predicate; return predicate;
} }
export function getDynamicSourcesForAllowUser(allow: boolean): DynamicValueSourceType[] {
const dynamicValueSourceTypes = [DynamicValueSourceType.CURRENT_TENANT,
DynamicValueSourceType.CURRENT_CUSTOMER];
if (allow) {
dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_USER);
} else {
dynamicValueSourceTypes.push(DynamicValueSourceType.CURRENT_DEVICE);
}
return dynamicValueSourceTypes;
}
export enum FilterPredicateType { export enum FilterPredicateType {
STRING = 'STRING', STRING = 'STRING',
NUMERIC = 'NUMERIC', NUMERIC = 'NUMERIC',
@ -289,6 +300,10 @@ export const dynamicValueSourceTypeTranslationMap = new Map<DynamicValueSourceTy
] ]
); );
export const inheritModeForDynamicValueSourceType = [
DynamicValueSourceType.CURRENT_CUSTOMER,
DynamicValueSourceType.CURRENT_DEVICE];
export interface DynamicValue<T> { export interface DynamicValue<T> {
sourceType: DynamicValueSourceType; sourceType: DynamicValueSourceType;
sourceAttribute: string; sourceAttribute: string;

4
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -1178,6 +1178,7 @@
"condition-type-simple": "Simple", "condition-type-simple": "Simple",
"condition-type-duration": "Duration", "condition-type-duration": "Duration",
"condition-during": "During {{during}}", "condition-during": "During {{during}}",
"condition-during-dynamic": "During \"{{ attribute }}\" ({{during}})",
"condition-type-repeating": "Repeating", "condition-type-repeating": "Repeating",
"condition-type-required": "Condition type is required.", "condition-type-required": "Condition type is required.",
"condition-repeating-value": "Count of events", "condition-repeating-value": "Count of events",
@ -1185,6 +1186,7 @@
"condition-repeating-value-pattern": "Count of events should be integers.", "condition-repeating-value-pattern": "Count of events should be integers.",
"condition-repeating-value-required": "Count of events is required.", "condition-repeating-value-required": "Count of events is required.",
"condition-repeat-times": "Repeats { count, plural, 1 {1 time} other {# times} }", "condition-repeat-times": "Repeats { count, plural, 1 {1 time} other {# times} }",
"condition-repeat-times-dynamic": "Repeats \"{ attribute }\" ({ count, plural, 1 {1 time} other {# times} })",
"schedule-type": "Scheduler type", "schedule-type": "Scheduler type",
"schedule-type-required": "Scheduler type is required.", "schedule-type-required": "Scheduler type is required.",
"schedule": "Schedule", "schedule": "Schedule",
@ -2268,7 +2270,7 @@
"expired-password-reset-message": "Your credentials has been expired! Please create new password.", "expired-password-reset-message": "Your credentials has been expired! Please create new password.",
"new-password": "New password", "new-password": "New password",
"new-password-again": "New password again", "new-password-again": "New password again",
"password-link-sent-message": "Password reset link was successfully sent!", "password-link-sent-message": "Reset link has been sent",
"email": "Email", "email": "Email",
"login-with": "Login with {{name}}", "login-with": "Login with {{name}}",
"or": "or", "or": "or",

Loading…
Cancel
Save