Browse Source

Merge af47a5b8c0 into be3207ab65

pull/15678/merge
Dmytro Skarzhynets 5 days ago
committed by GitHub
parent
commit
c8064c097e
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java
  2. 31
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/DashboardConfig.java
  3. 41
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/EntityAlias.java
  4. 50
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAliasFilter.java
  5. 30
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardApiUsageStateFilter.java
  6. 34
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetSearchQueryFilter.java
  7. 36
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetTypeFilter.java
  8. 34
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceSearchQueryFilter.java
  9. 36
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceTypeFilter.java
  10. 34
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeSearchQueryFilter.java
  11. 36
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeTypeFilter.java
  12. 39
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityListFilter.java
  13. 36
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityNameFilter.java
  14. 49
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java
  15. 32
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityTypeFilter.java
  16. 34
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewSearchQueryFilter.java
  17. 36
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewTypeFilter.java
  18. 54
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java
  19. 32
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardSingleEntityFilter.java
  20. 32
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStateEntityFilter.java
  21. 34
      common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java
  22. 4
      dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java
  23. 88
      dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java
  24. 75
      dao/src/test/java/org/thingsboard/server/dao/service/validator/AbstractDashboardDataValidatorTest.java
  25. 56
      dao/src/test/java/org/thingsboard/server/dao/service/validator/DashboardDataValidatorTest.java
  26. 43
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardApiUsageStateFilterValidationTest.java
  27. 75
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardAssetSearchQueryFilterValidationTest.java
  28. 57
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardAssetTypeFilterValidationTest.java
  29. 314
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardConfigurationStructureTest.java
  30. 75
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardDeviceSearchQueryFilterValidationTest.java
  31. 57
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardDeviceTypeFilterValidationTest.java
  32. 75
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEdgeSearchQueryFilterValidationTest.java
  33. 57
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEdgeTypeFilterValidationTest.java
  34. 57
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityListFilterValidationTest.java
  35. 57
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityNameFilterValidationTest.java
  36. 47
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityTypeFilterValidationTest.java
  37. 75
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityViewSearchQueryFilterValidationTest.java
  38. 57
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityViewTypeFilterValidationTest.java
  39. 98
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardRelationsQueryFilterValidationTest.java
  40. 55
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardSingleEntityFilterValidationTest.java
  41. 47
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardStateEntityFilterValidationTest.java
  42. 38
      dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardViolationAggregationTest.java

2
application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java

@ -372,6 +372,8 @@ public class VersionControlTest extends AbstractControllerTest {
"\"" + aliasId + "\": {\n" +
"\"alias\": \"assets\",\n" +
"\"filter\": {\n" +
" \"type\": \"entityList\",\n" +
" \"entityType\": \"ASSET\",\n" +
" \"entityList\": [\n" +
" \"" + asset1.getId() + "\",\n" +
" \"" + asset2.getId() + "\",\n" +

31
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/DashboardConfig.java

@ -0,0 +1,31 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.Valid;
import lombok.Data;
import java.util.Map;
import java.util.UUID;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class DashboardConfig {
private Map<UUID, @Valid EntityAlias> entityAliases;
}

41
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/EntityAlias.java

@ -0,0 +1,41 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import org.thingsboard.server.common.data.dashboard.filter.DashboardAliasFilter;
import java.util.UUID;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class EntityAlias {
@NotNull
private UUID id;
@NotBlank
private String alias;
@NotNull
@Valid
private DashboardAliasFilter filter;
}

50
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAliasFilter.java

@ -0,0 +1,50 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = DashboardSingleEntityFilter.class, name = "singleEntity"),
@JsonSubTypes.Type(value = DashboardEntityListFilter.class, name = "entityList"),
@JsonSubTypes.Type(value = DashboardEntityNameFilter.class, name = "entityName"),
@JsonSubTypes.Type(value = DashboardEntityTypeFilter.class, name = "entityType"),
@JsonSubTypes.Type(value = DashboardStateEntityFilter.class, name = "stateEntity"),
@JsonSubTypes.Type(value = DashboardAssetTypeFilter.class, name = "assetType"),
@JsonSubTypes.Type(value = DashboardDeviceTypeFilter.class, name = "deviceType"),
@JsonSubTypes.Type(value = DashboardEdgeTypeFilter.class, name = "edgeType"),
@JsonSubTypes.Type(value = DashboardEntityViewTypeFilter.class, name = "entityViewType"),
@JsonSubTypes.Type(value = DashboardApiUsageStateFilter.class, name = "apiUsageState"),
@JsonSubTypes.Type(value = DashboardRelationsQueryFilter.class, name = "relationsQuery"),
@JsonSubTypes.Type(value = DashboardAssetSearchQueryFilter.class, name = "assetSearchQuery"),
@JsonSubTypes.Type(value = DashboardDeviceSearchQueryFilter.class, name = "deviceSearchQuery"),
@JsonSubTypes.Type(value = DashboardEntityViewSearchQueryFilter.class, name = "entityViewSearchQuery"),
@JsonSubTypes.Type(value = DashboardEdgeSearchQueryFilter.class, name = "edgeSearchQuery")
})
public abstract class DashboardAliasFilter {
private boolean resolveMultiple;
}

30
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardApiUsageStateFilter.java

@ -0,0 +1,30 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.thingsboard.server.common.data.id.CustomerId;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardApiUsageStateFilter extends DashboardAliasFilter {
private CustomerId customerId;
}

34
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetSearchQueryFilter.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardAssetSearchQueryFilter extends DashboardEntitySearchQueryFilter {
@NotEmpty
private List<@NotBlank String> assetTypes;
}

36
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetTypeFilter.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardAssetTypeFilter extends DashboardAliasFilter {
@NotEmpty
private List<@NotBlank String> assetTypes;
private String assetNameFilter;
}

34
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceSearchQueryFilter.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardDeviceSearchQueryFilter extends DashboardEntitySearchQueryFilter {
@NotEmpty
private List<@NotBlank String> deviceTypes;
}

36
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceTypeFilter.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardDeviceTypeFilter extends DashboardAliasFilter {
@NotEmpty
private List<@NotBlank String> deviceTypes;
private String deviceNameFilter;
}

34
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeSearchQueryFilter.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardEdgeSearchQueryFilter extends DashboardEntitySearchQueryFilter {
@NotEmpty
private List<@NotBlank String> edgeTypes;
}

36
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeTypeFilter.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardEdgeTypeFilter extends DashboardAliasFilter {
@NotEmpty
private List<@NotBlank String> edgeTypes;
private String edgeNameFilter;
}

39
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityListFilter.java

@ -0,0 +1,39 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.thingsboard.server.common.data.EntityType;
import java.util.List;
import java.util.UUID;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardEntityListFilter extends DashboardAliasFilter {
@NotNull
private EntityType entityType;
@NotEmpty
private List<@NotNull UUID> entityList;
}

36
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityNameFilter.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.thingsboard.server.common.data.EntityType;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardEntityNameFilter extends DashboardAliasFilter {
@NotNull
private EntityType entityType;
@NotBlank
private String entityNameFilter;
}

49
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java

@ -0,0 +1,49 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.thingsboard.server.common.data.query.AliasEntityId;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public abstract class DashboardEntitySearchQueryFilter extends DashboardAliasFilter implements DashboardStatefulRootFilter {
private AliasEntityId rootEntity;
private boolean rootStateEntity;
private String stateEntityParamName;
private AliasEntityId defaultStateEntity;
private String relationType;
@NotNull
private EntitySearchDirection direction;
@PositiveOrZero
private int maxLevel;
private boolean fetchLastLevelOnly;
}

32
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityTypeFilter.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.thingsboard.server.common.data.EntityType;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardEntityTypeFilter extends DashboardAliasFilter {
@NotNull
private EntityType entityType;
}

34
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewSearchQueryFilter.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardEntityViewSearchQueryFilter extends DashboardEntitySearchQueryFilter {
@NotEmpty
private List<@NotBlank String> entityViewTypes;
}

36
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewTypeFilter.java

@ -0,0 +1,36 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardEntityViewTypeFilter extends DashboardAliasFilter {
@NotEmpty
private List<@NotBlank String> entityViewTypes;
private String entityViewNameFilter;
}

54
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java

@ -0,0 +1,54 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.thingsboard.server.common.data.query.AliasEntityId;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter;
import java.util.List;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardRelationsQueryFilter extends DashboardAliasFilter implements DashboardStatefulRootFilter {
private AliasEntityId rootEntity;
private boolean rootStateEntity;
private String stateEntityParamName;
private AliasEntityId defaultStateEntity;
@NotNull
private EntitySearchDirection direction;
@PositiveOrZero
private int maxLevel;
private boolean fetchLastLevelOnly;
@Valid
private List<RelationEntityTypeFilter> filters;
}

32
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardSingleEntityFilter.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.thingsboard.server.common.data.query.AliasEntityId;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardSingleEntityFilter extends DashboardAliasFilter {
@NotNull
private AliasEntityId singleEntity;
}

32
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStateEntityFilter.java

@ -0,0 +1,32 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.thingsboard.server.common.data.query.AliasEntityId;
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class DashboardStateEntityFilter extends DashboardAliasFilter {
private String stateEntityParamName;
private AliasEntityId defaultStateEntity;
}

34
common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java

@ -0,0 +1,34 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.common.data.dashboard.filter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.validation.constraints.AssertTrue;
import org.thingsboard.server.common.data.query.AliasEntityId;
public interface DashboardStatefulRootFilter {
AliasEntityId getRootEntity();
boolean isRootStateEntity();
@AssertTrue(message = "must include 'rootEntity' when 'rootStateEntity' is false")
@JsonIgnore
default boolean isValidRootEntity() {
return isRootStateEntity() || getRootEntity() != null;
}
}

4
dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java

@ -61,6 +61,10 @@ public class ConstraintValidator {
}
}
public static <T> Set<ConstraintViolation<T>> getViolations(T data) {
return fieldsValidator.validate(data);
}
public static String getErrorMessage(Collection<ConstraintViolation<Object>> constraintsViolations) {
return constraintsViolations.stream()
.map(ConstraintValidator::getErrorMessage)

88
dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java

@ -15,20 +15,33 @@
*/
package org.thingsboard.server.dao.service.validator;
import org.springframework.beans.factory.annotation.Autowired;
import com.fasterxml.jackson.databind.JsonNode;
import jakarta.validation.ConstraintViolation;
import jakarta.validation.ElementKind;
import jakarta.validation.Path;
import jakarta.validation.constraints.AssertTrue;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.dashboard.DashboardConfig;
import org.thingsboard.server.common.data.dashboard.EntityAlias;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.service.ConstraintValidator;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
@Component
@RequiredArgsConstructor
public class DashboardDataValidator extends DataValidator<Dashboard> {
@Autowired
private TenantService tenantService;
private final TenantService tenantService;
@Override
protected void validateCreate(TenantId tenantId, Dashboard data) {
@ -45,5 +58,74 @@ public class DashboardDataValidator extends DataValidator<Dashboard> {
throw new DataValidationException("Dashboard is referencing to non-existent tenant!");
}
}
if (dashboard.getConfiguration() == null || !dashboard.getConfiguration().isObject()) {
return;
}
JsonNode aliasesNode = dashboard.getConfiguration().get("entityAliases");
if (aliasesNode == null || aliasesNode.isNull()) {
return;
}
DashboardConfig parsed;
try {
parsed = JacksonUtil.OBJECT_MAPPER.convertValue(dashboard.getConfiguration(), DashboardConfig.class);
} catch (IllegalArgumentException e) {
throw new DataValidationException("Dashboard configuration has invalid structure: " + e.getMessage());
}
validateEntityAliases(parsed);
}
private static void validateEntityAliases(DashboardConfig config) {
if (config.getEntityAliases() == null) {
return;
}
Set<ConstraintViolation<DashboardConfig>> violations = ConstraintValidator.getViolations(config);
if (!violations.isEmpty()) {
throw new DataValidationException(violations.stream()
.map(v -> formatViolation(v, config))
.distinct()
.sorted()
.collect(Collectors.joining(", ", "Dashboard validation error: ", "")));
}
config.getEntityAliases().forEach(DashboardDataValidator::validateEntityAliasKey);
}
private static String formatViolation(ConstraintViolation<DashboardConfig> v, DashboardConfig cfg) {
UUID aliasKey = null;
String fieldName = null;
Integer elementIndex = null;
for (Path.Node node : v.getPropertyPath()) {
if (node.getKey() instanceof UUID uuid) {
aliasKey = uuid;
}
if (node.getKind() == ElementKind.CONTAINER_ELEMENT) {
if (node.getIndex() != null) {
elementIndex = node.getIndex();
}
} else if (node.getName() != null) {
fieldName = node.getName();
}
}
String elementInfo = elementIndex != null ? " element at index " + elementIndex : "";
boolean isLogicConstraint = v.getConstraintDescriptor().getAnnotation().annotationType().equals(AssertTrue.class);
if (aliasKey != null) {
EntityAlias alias = cfg.getEntityAliases().get(aliasKey);
String aliasName = alias != null && alias.getAlias() != null && !alias.getAlias().isBlank()
? alias.getAlias() : aliasKey.toString();
if (isLogicConstraint) {
return "alias '" + aliasName + "' " + v.getMessage();
}
return "alias '" + aliasName + "' field '" + fieldName + "'" + elementInfo + " " + v.getMessage();
}
if (isLogicConstraint) {
return v.getMessage();
}
return "field '" + fieldName + "'" + elementInfo + " " + v.getMessage();
}
private static void validateEntityAliasKey(UUID key, EntityAlias alias) {
if (!alias.getId().equals(key)) {
throw new DataValidationException("Dashboard validation error: alias '" + alias.getAlias() + "' has 'id' that does not match its key!");
}
}
}

75
dao/src/test/java/org/thingsboard/server/dao/service/validator/AbstractDashboardDataValidatorTest.java

@ -0,0 +1,75 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator;
import com.fasterxml.jackson.databind.JsonNode;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.bean.override.mockito.MockitoBean;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.UUID;
import static org.mockito.BDDMockito.willReturn;
@SpringBootTest(classes = DashboardDataValidator.class)
public abstract class AbstractDashboardDataValidatorTest {
protected static final String ALIAS_UUID = "a1ddb8fa-90ff-5598-e7f2-e254194d055d";
@MockitoBean
protected TenantService tenantService;
@Autowired
protected DashboardDataValidator validator;
protected final TenantId tenantId = TenantId.fromUUID(UUID.fromString("9ef79cdf-37a8-4119-b682-2e7ed4e018da"));
@BeforeEach
void setUp() {
willReturn(true).given(tenantService).tenantExists(tenantId);
}
protected Dashboard dashboardWith(JsonNode configuration) {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("test dashboard");
dashboard.setTenantId(tenantId);
dashboard.setConfiguration(configuration);
return dashboard;
}
protected Dashboard filterAlias(String aliasName, String filterJson) {
String json = """
{
"entityAliases": {
"%s": {
"id": "%s",
"alias": "%s",
"filter": %s
}
}
}""".formatted(ALIAS_UUID, ALIAS_UUID, aliasName, filterJson);
return dashboardWith(JacksonUtil.toJsonNode(json));
}
protected void validate(Dashboard dashboard) {
validator.validateDataImpl(tenantId, dashboard);
}
}

56
dao/src/test/java/org/thingsboard/server/dao/service/validator/DashboardDataValidatorTest.java

@ -1,56 +0,0 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.boot.test.mock.mockito.SpyBean;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.tenant.TenantService;
import java.util.UUID;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.verify;
@SpringBootTest(classes = DashboardDataValidator.class)
class DashboardDataValidatorTest {
@MockBean
TenantService tenantService;
@SpyBean
DashboardDataValidator validator;
TenantId tenantId = TenantId.fromUUID(UUID.fromString("9ef79cdf-37a8-4119-b682-2e7ed4e018da"));
@BeforeEach
void setUp() {
willReturn(true).given(tenantService).tenantExists(tenantId);
}
@Test
void testValidateNameInvocation() {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("flight control");
dashboard.setTenantId(tenantId);
validator.validateDataImpl(tenantId, dashboard);
verify(validator).validateString("Dashboard title", dashboard.getTitle());
}
}

43
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardApiUsageStateFilterValidationTest.java

@ -0,0 +1,43 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import static org.assertj.core.api.Assertions.assertThatCode;
class DashboardApiUsageStateFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptApiUsageStateFilterWithoutCustomerId() {
Dashboard dashboard = filterAlias("Tenant Usage", """
{"type": "apiUsageState"}""");
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldAcceptApiUsageStateFilterWithCustomerId() {
Dashboard dashboard = filterAlias("Customer Usage", """
{"type": "apiUsageState", "customerId": {"entityType": "CUSTOMER", "id": "%s"}}""".formatted(ALIAS_UUID));
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
}

75
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardAssetSearchQueryFilterValidationTest.java

@ -0,0 +1,75 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardAssetSearchQueryFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptValidAssetSearchQueryFilter() {
Dashboard dashboard = filterAlias("Assets", """
{
"type": "assetSearchQuery",
"rootEntity": {"entityType": "DEVICE", "id": "%s"},
"direction": "FROM",
"maxLevel": 1,
"assetTypes": ["thermostat"]
}""".formatted(ALIAS_UUID));
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectAssetSearchQueryFilterWithEmptyAssetTypes() {
Dashboard dashboard = filterAlias("Assets", """
{
"type": "assetSearchQuery",
"rootEntity": {"entityType": "DEVICE", "id": "%s"},
"direction": "FROM",
"maxLevel": 1,
"assetTypes": []
}""".formatted(ALIAS_UUID));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Assets' field 'assetTypes' must not be empty");
}
@Test
void shouldRejectAssetSearchQueryFilterWithBlankAssetTypeElement() {
Dashboard dashboard = filterAlias("Assets", """
{
"type": "assetSearchQuery",
"rootEntity": {"entityType": "DEVICE", "id": "%s"},
"direction": "FROM",
"maxLevel": 1,
"assetTypes": ["thermostat", " "]
}""".formatted(ALIAS_UUID));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Assets' field 'assetTypes' element at index 1 must not be blank");
}
}

57
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardAssetTypeFilterValidationTest.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardAssetTypeFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptValidAssetTypeFilter() {
Dashboard dashboard = filterAlias("Assets", """
{"type": "assetType", "assetTypes": ["thermostat", "valve"]}""");
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectAssetTypeFilterWithEmptyAssetTypes() {
Dashboard dashboard = filterAlias("Assets", """
{"type": "assetType", "assetTypes": []}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Assets' field 'assetTypes' must not be empty");
}
@Test
void shouldRejectAssetTypeFilterWithBlankAssetTypeElement() {
Dashboard dashboard = filterAlias("Assets", """
{"type": "assetType", "assetTypes": ["thermostat", " "]}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Assets' field 'assetTypes' element at index 1 must not be blank");
}
}

314
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardConfigurationStructureTest.java

@ -0,0 +1,314 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardConfigurationStructureTest extends AbstractDashboardDataValidatorTest {
@Nested
class Title {
@Test
void shouldAcceptValidTitle() {
Dashboard dashboard = new Dashboard();
dashboard.setTitle("flight control");
dashboard.setTenantId(tenantId);
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectBlankTitle() {
Dashboard dashboard = new Dashboard();
dashboard.setTitle(" ");
dashboard.setTenantId(tenantId);
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard title should be specified!");
}
}
@Nested
class Tenant {
@Test
void shouldRejectDashboardReferencingNonExistentTenant() {
TenantId unknownTenantId = TenantId.fromUUID(UUID.fromString("11111111-1111-1111-1111-111111111111"));
Dashboard dashboard = new Dashboard();
dashboard.setTitle("flight control");
dashboard.setTenantId(unknownTenantId);
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard is referencing to non-existent tenant!");
}
}
@Nested
class ConfigurationShape {
@Test
void shouldAcceptDashboardWithoutConfiguration() {
Dashboard dashboard = dashboardWith(null);
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldAcceptNonObjectConfiguration() {
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode("\"just a string\""));
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldAcceptConfigurationWithoutEntityAliases() {
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode("{\"widgets\": {}}"));
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldAcceptEmptyEntityAliasesMap() {
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode("{\"entityAliases\": {}}"));
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldAcceptNullEntityAliases() {
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode("{\"entityAliases\": null}"));
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectEntityAliasesAsArray() {
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode("{\"entityAliases\": []}"));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessageStartingWith("Dashboard configuration has invalid structure:");
}
}
@Nested
class EntityAliasShape {
@Test
void shouldAcceptWellFormedEntityAlias() {
Dashboard dashboard = filterAlias("Devices", """
{"type": "entityType", "entityType": "DEVICE"}""");
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectBlankAliasDisplayName() {
String json = """
{
"entityAliases": {
"%s": {
"id": "%s",
"alias": "",
"filter": {"type": "entityType", "entityType": "DEVICE"}
}
}
}""".formatted(ALIAS_UUID, ALIAS_UUID);
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode(json));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias '" + ALIAS_UUID + "' field 'alias' must not be blank");
}
@Test
void shouldRejectKeyThatIsNotUuid() {
String json = """
{"entityAliases": {"not-a-uuid": {"id": "%s", "alias": "X", "filter": {"type": "entityType", "entityType": "DEVICE"}}}}""".formatted(ALIAS_UUID);
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode(json));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessageStartingWith("Dashboard configuration has invalid structure:");
}
@Test
void shouldRejectAliasValueThatIsNotObject() {
String json = """
{"entityAliases": {"%s": "not an object"}}""".formatted(ALIAS_UUID);
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode(json));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessageStartingWith("Dashboard configuration has invalid structure:");
}
@Test
void shouldRejectMissingIdField() {
String json = """
{
"entityAliases": {
"%s": {"alias": "Devices", "filter": {"type": "entityType", "entityType": "DEVICE"}}
}
}""".formatted(ALIAS_UUID);
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode(json));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Devices' field 'id' must not be null");
}
@Test
void shouldRejectNonTextualIdField() {
String json = """
{
"entityAliases": {
"%s": {"id": 42, "alias": "Devices", "filter": {"type": "entityType", "entityType": "DEVICE"}}
}
}""".formatted(ALIAS_UUID);
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode(json));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessageStartingWith("Dashboard configuration has invalid structure:");
}
@Test
void shouldRejectIdThatIsNotUuid() {
String json = """
{
"entityAliases": {
"%s": {"id": "not-a-uuid", "alias": "Devices", "filter": {"type": "entityType", "entityType": "DEVICE"}}
}
}""".formatted(ALIAS_UUID);
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode(json));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessageStartingWith("Dashboard configuration has invalid structure:");
}
@Test
void shouldRejectIdNotMatchingKey() {
String json = """
{
"entityAliases": {
"%s": {"id": "11111111-1111-1111-1111-111111111111", "alias": "Devices", "filter": {"type": "entityType", "entityType": "DEVICE"}}
}
}""".formatted(ALIAS_UUID);
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode(json));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Devices' has 'id' that does not match its key!");
}
@Test
void shouldRejectMissingAliasField() {
String json = """
{
"entityAliases": {
"%s": {"id": "%s", "filter": {"type": "entityType", "entityType": "DEVICE"}}
}
}""".formatted(ALIAS_UUID, ALIAS_UUID);
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode(json));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias '" + ALIAS_UUID + "' field 'alias' must not be blank");
}
@Test
void shouldRejectMissingFilterField() {
String json = """
{
"entityAliases": {
"%s": {"id": "%s", "alias": "Devices"}
}
}""".formatted(ALIAS_UUID, ALIAS_UUID);
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode(json));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Devices' field 'filter' must not be null");
}
@Test
void shouldRejectFilterWithoutType() {
String json = """
{
"entityAliases": {
"%s": {"id": "%s", "alias": "Devices", "filter": {}}
}
}""".formatted(ALIAS_UUID, ALIAS_UUID);
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode(json));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessageStartingWith("Dashboard configuration has invalid structure:");
}
@Test
void shouldRejectFilterWithUnknownType() {
String json = """
{
"entityAliases": {
"%s": {"id": "%s", "alias": "Devices", "filter": {"type": "bogus"}}
}
}""".formatted(ALIAS_UUID, ALIAS_UUID);
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode(json));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessageStartingWith("Dashboard configuration has invalid structure:");
}
@Test
void shouldRejectNonObjectFilterField() {
String json = """
{
"entityAliases": {
"%s": {"id": "%s", "alias": "Devices", "filter": "not an object"}
}
}""".formatted(ALIAS_UUID, ALIAS_UUID);
Dashboard dashboard = dashboardWith(JacksonUtil.toJsonNode(json));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessageStartingWith("Dashboard configuration has invalid structure:");
}
}
}

75
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardDeviceSearchQueryFilterValidationTest.java

@ -0,0 +1,75 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardDeviceSearchQueryFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptValidDeviceSearchQueryFilter() {
Dashboard dashboard = filterAlias("Devices", """
{
"type": "deviceSearchQuery",
"rootEntity": {"entityType": "ASSET", "id": "%s"},
"direction": "FROM",
"maxLevel": 1,
"deviceTypes": ["thermostat"]
}""".formatted(ALIAS_UUID));
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectDeviceSearchQueryFilterWithEmptyDeviceTypes() {
Dashboard dashboard = filterAlias("Devices", """
{
"type": "deviceSearchQuery",
"rootEntity": {"entityType": "ASSET", "id": "%s"},
"direction": "FROM",
"maxLevel": 1,
"deviceTypes": []
}""".formatted(ALIAS_UUID));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Devices' field 'deviceTypes' must not be empty");
}
@Test
void shouldRejectDeviceSearchQueryFilterWithBlankDeviceTypeElement() {
Dashboard dashboard = filterAlias("Devices", """
{
"type": "deviceSearchQuery",
"rootEntity": {"entityType": "ASSET", "id": "%s"},
"direction": "FROM",
"maxLevel": 1,
"deviceTypes": ["thermostat", " "]
}""".formatted(ALIAS_UUID));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Devices' field 'deviceTypes' element at index 1 must not be blank");
}
}

57
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardDeviceTypeFilterValidationTest.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardDeviceTypeFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptValidDeviceTypeFilter() {
Dashboard dashboard = filterAlias("Devices", """
{"type": "deviceType", "deviceTypes": ["thermostat", "valve"]}""");
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectDeviceTypeFilterWithEmptyDeviceTypes() {
Dashboard dashboard = filterAlias("Devices", """
{"type": "deviceType", "deviceTypes": []}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Devices' field 'deviceTypes' must not be empty");
}
@Test
void shouldRejectDeviceTypeFilterWithBlankDeviceTypeElement() {
Dashboard dashboard = filterAlias("Devices", """
{"type": "deviceType", "deviceTypes": ["thermostat", " "]}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Devices' field 'deviceTypes' element at index 1 must not be blank");
}
}

75
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEdgeSearchQueryFilterValidationTest.java

@ -0,0 +1,75 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardEdgeSearchQueryFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptValidEdgeSearchQueryFilter() {
Dashboard dashboard = filterAlias("Edges", """
{
"type": "edgeSearchQuery",
"rootEntity": {"entityType": "TENANT", "id": "%s"},
"direction": "FROM",
"maxLevel": 1,
"edgeTypes": ["gateway"]
}""".formatted(ALIAS_UUID));
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectEdgeSearchQueryFilterWithEmptyEdgeTypes() {
Dashboard dashboard = filterAlias("Edges", """
{
"type": "edgeSearchQuery",
"rootEntity": {"entityType": "TENANT", "id": "%s"},
"direction": "FROM",
"maxLevel": 1,
"edgeTypes": []
}""".formatted(ALIAS_UUID));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Edges' field 'edgeTypes' must not be empty");
}
@Test
void shouldRejectEdgeSearchQueryFilterWithBlankEdgeTypeElement() {
Dashboard dashboard = filterAlias("Edges", """
{
"type": "edgeSearchQuery",
"rootEntity": {"entityType": "TENANT", "id": "%s"},
"direction": "FROM",
"maxLevel": 1,
"edgeTypes": ["gateway", " "]
}""".formatted(ALIAS_UUID));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Edges' field 'edgeTypes' element at index 1 must not be blank");
}
}

57
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEdgeTypeFilterValidationTest.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardEdgeTypeFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptValidEdgeTypeFilter() {
Dashboard dashboard = filterAlias("Edges", """
{"type": "edgeType", "edgeTypes": ["gateway"]}""");
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectEdgeTypeFilterWithEmptyEdgeTypes() {
Dashboard dashboard = filterAlias("Edges", """
{"type": "edgeType", "edgeTypes": []}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Edges' field 'edgeTypes' must not be empty");
}
@Test
void shouldRejectEdgeTypeFilterWithBlankEdgeTypeElement() {
Dashboard dashboard = filterAlias("Edges", """
{"type": "edgeType", "edgeTypes": ["gateway", " "]}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Edges' field 'edgeTypes' element at index 1 must not be blank");
}
}

57
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityListFilterValidationTest.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardEntityListFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptValidEntityListFilter() {
Dashboard dashboard = filterAlias("Devices", """
{"type": "entityList", "entityType": "DEVICE", "entityList": ["11111111-1111-1111-1111-111111111111"]}""");
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectEntityListFilterWithEmptyEntityList() {
Dashboard dashboard = filterAlias("Devices", """
{"type": "entityList", "entityType": "DEVICE", "entityList": []}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Devices' field 'entityList' must not be empty");
}
@Test
void shouldRejectEntityListFilterWithoutEntityType() {
Dashboard dashboard = filterAlias("Devices", """
{"type": "entityList", "entityList": ["11111111-1111-1111-1111-111111111111"]}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Devices' field 'entityType' must not be null");
}
}

57
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityNameFilterValidationTest.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardEntityNameFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptValidEntityNameFilter() {
Dashboard dashboard = filterAlias("Sensors", """
{"type": "entityName", "entityType": "DEVICE", "entityNameFilter": "sensor"}""");
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectEntityNameFilterWithoutEntityType() {
Dashboard dashboard = filterAlias("Sensors", """
{"type": "entityName", "entityNameFilter": "sensor"}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Sensors' field 'entityType' must not be null");
}
@Test
void shouldRejectEntityNameFilterWithBlankEntityNameFilter() {
Dashboard dashboard = filterAlias("Sensors", """
{"type": "entityName", "entityType": "DEVICE", "entityNameFilter": " "}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Sensors' field 'entityNameFilter' must not be blank");
}
}

47
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityTypeFilterValidationTest.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardEntityTypeFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptValidEntityTypeFilter() {
Dashboard dashboard = filterAlias("Devices", """
{"type": "entityType", "entityType": "DEVICE"}""");
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectEntityTypeFilterWithoutEntityType() {
Dashboard dashboard = filterAlias("Devices", """
{"type": "entityType"}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Devices' field 'entityType' must not be null");
}
}

75
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityViewSearchQueryFilterValidationTest.java

@ -0,0 +1,75 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardEntityViewSearchQueryFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptValidEntityViewSearchQueryFilter() {
Dashboard dashboard = filterAlias("Views", """
{
"type": "entityViewSearchQuery",
"rootEntity": {"entityType": "DEVICE", "id": "%s"},
"direction": "FROM",
"maxLevel": 1,
"entityViewTypes": ["summary"]
}""".formatted(ALIAS_UUID));
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectEntityViewSearchQueryFilterWithEmptyEntityViewTypes() {
Dashboard dashboard = filterAlias("Views", """
{
"type": "entityViewSearchQuery",
"rootEntity": {"entityType": "DEVICE", "id": "%s"},
"direction": "FROM",
"maxLevel": 1,
"entityViewTypes": []
}""".formatted(ALIAS_UUID));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Views' field 'entityViewTypes' must not be empty");
}
@Test
void shouldRejectEntityViewSearchQueryFilterWithBlankEntityViewTypeElement() {
Dashboard dashboard = filterAlias("Views", """
{
"type": "entityViewSearchQuery",
"rootEntity": {"entityType": "DEVICE", "id": "%s"},
"direction": "FROM",
"maxLevel": 1,
"entityViewTypes": ["summary", " "]
}""".formatted(ALIAS_UUID));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Views' field 'entityViewTypes' element at index 1 must not be blank");
}
}

57
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityViewTypeFilterValidationTest.java

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardEntityViewTypeFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptValidEntityViewTypeFilter() {
Dashboard dashboard = filterAlias("Views", """
{"type": "entityViewType", "entityViewTypes": ["summary"]}""");
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectEntityViewTypeFilterWithEmptyEntityViewTypes() {
Dashboard dashboard = filterAlias("Views", """
{"type": "entityViewType", "entityViewTypes": []}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Views' field 'entityViewTypes' must not be empty");
}
@Test
void shouldRejectEntityViewTypeFilterWithBlankEntityViewTypeElement() {
Dashboard dashboard = filterAlias("Views", """
{"type": "entityViewType", "entityViewTypes": ["summary", " "]}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Views' field 'entityViewTypes' element at index 1 must not be blank");
}
}

98
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardRelationsQueryFilterValidationTest.java

@ -0,0 +1,98 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardRelationsQueryFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptValidRelationsQueryFilter() {
Dashboard dashboard = filterAlias("Related", """
{
"type": "relationsQuery",
"rootEntity": {"entityType": "DEVICE", "id": "%s"},
"direction": "FROM",
"maxLevel": 1,
"filters": []
}""".formatted(ALIAS_UUID));
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldAcceptRelationsQueryFilterWithStateDrivenRoot() {
Dashboard dashboard = filterAlias("Related", """
{
"type": "relationsQuery",
"rootStateEntity": true,
"direction": "FROM",
"maxLevel": 1
}""");
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldAcceptRelationsQueryFilterWithZeroMaxLevel() {
// maxLevel = 0 means "unlimited" in the UI
Dashboard dashboard = filterAlias("Related", """
{
"type": "relationsQuery",
"rootEntity": {"entityType": "DEVICE", "id": "%s"},
"direction": "FROM",
"maxLevel": 0
}""".formatted(ALIAS_UUID));
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectRelationsQueryFilterWithoutDirection() {
Dashboard dashboard = filterAlias("Related", """
{
"type": "relationsQuery",
"rootEntity": {"entityType": "DEVICE", "id": "%s"},
"maxLevel": 1
}""".formatted(ALIAS_UUID));
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Related' field 'direction' must not be null");
}
@Test
void shouldRejectRelationsQueryFilterWithoutRootEntityWhenStateDrivenIsFalse() {
Dashboard dashboard = filterAlias("Related", """
{
"type": "relationsQuery",
"direction": "FROM",
"maxLevel": 1
}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Related' must include 'rootEntity' when 'rootStateEntity' is false");
}
}

55
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardSingleEntityFilterValidationTest.java

@ -0,0 +1,55 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardSingleEntityFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptValidSingleEntityFilter() {
Dashboard dashboard = filterAlias("Sensor", """
{"type": "singleEntity", "singleEntity": {"entityType": "DEVICE", "id": "%s"}}""".formatted(ALIAS_UUID));
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldAcceptSingleEntityFilterWithAliasEntity() {
Dashboard dashboard = filterAlias("Current Tenant", """
{"type": "singleEntity", "singleEntity": {"entityType": "CURRENT_TENANT"}}""");
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldRejectSingleEntityFilterWithoutSingleEntity() {
Dashboard dashboard = filterAlias("Sensor", """
{"type": "singleEntity"}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Sensor' field 'singleEntity' must not be null");
}
}

47
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardStateEntityFilterValidationTest.java

@ -0,0 +1,47 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import static org.assertj.core.api.Assertions.assertThatCode;
class DashboardStateEntityFilterValidationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAcceptStateEntityFilterWithoutAnyFields() {
Dashboard dashboard = filterAlias("Selected Device", """
{"type": "stateEntity"}""");
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
@Test
void shouldAcceptStateEntityFilterWithBothFieldsSet() {
Dashboard dashboard = filterAlias("Selected Device", """
{
"type": "stateEntity",
"stateEntityParamName": "deviceId",
"defaultStateEntity": {"entityType": "DEVICE", "id": "%s"}
}""".formatted(ALIAS_UUID));
assertThatCode(() -> validate(dashboard)).doesNotThrowAnyException();
}
}

38
dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardViolationAggregationTest.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2026 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.
*/
package org.thingsboard.server.dao.service.validator.dashboard;
import org.thingsboard.server.dao.service.validator.AbstractDashboardDataValidatorTest;
import org.junit.jupiter.api.Test;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.dao.exception.DataValidationException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
class DashboardViolationAggregationTest extends AbstractDashboardDataValidatorTest {
@Test
void shouldAggregateMultipleViolations() {
Dashboard dashboard = filterAlias("Devices", """
{"type": "entityList", "entityList": []}""");
assertThatThrownBy(() -> validate(dashboard))
.isInstanceOf(DataValidationException.class)
.hasMessage("Dashboard validation error: alias 'Devices' field 'entityList' must not be empty, alias 'Devices' field 'entityType' must not be null");
}
}
Loading…
Cancel
Save