From ed391e12c3a93f5e042722256074f97b3ab60ebd Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Thu, 21 May 2026 11:42:29 +0300 Subject: [PATCH 1/4] feat: validate dashboard entityAliases filters --- .../data/dashboard/DashboardConfig.java | 31 ++ .../common/data/dashboard/EntityAlias.java | 41 +++ .../filter/DashboardAliasFilter.java | 44 +++ .../filter/DashboardApiUsageStateFilter.java | 26 ++ .../DashboardAssetSearchQueryFilter.java | 34 ++ .../filter/DashboardAssetTypeFilter.java | 32 ++ .../DashboardDeviceSearchQueryFilter.java | 34 ++ .../filter/DashboardDeviceTypeFilter.java | 32 ++ .../DashboardEdgeSearchQueryFilter.java | 34 ++ .../filter/DashboardEdgeTypeFilter.java | 32 ++ .../filter/DashboardEntityListFilter.java | 35 ++ .../filter/DashboardEntityNameFilter.java | 32 ++ .../DashboardEntitySearchQueryFilter.java | 46 +++ .../filter/DashboardEntityTypeFilter.java | 28 ++ .../DashboardEntityViewSearchQueryFilter.java | 34 ++ .../filter/DashboardEntityViewTypeFilter.java | 32 ++ .../filter/DashboardRelationsQueryFilter.java | 51 +++ .../filter/DashboardSingleEntityFilter.java | 28 ++ .../filter/DashboardStateEntityFilter.java | 28 ++ .../filter/DashboardStatefulRoot.java | 58 ++++ .../filter/DashboardStatefulRootFilter.java | 26 ++ .../dao/service/ConstraintValidator.java | 4 + .../validator/DashboardDataValidator.java | 80 ++++- .../AbstractDashboardDataValidatorTest.java | 75 +++++ .../validator/DashboardDataValidatorTest.java | 56 ---- ...oardApiUsageStateFilterValidationTest.java | 43 +++ ...dAssetSearchQueryFilterValidationTest.java | 75 +++++ ...ashboardAssetTypeFilterValidationTest.java | 57 ++++ .../DashboardConfigurationStructureTest.java | 314 ++++++++++++++++++ ...DeviceSearchQueryFilterValidationTest.java | 75 +++++ ...shboardDeviceTypeFilterValidationTest.java | 57 ++++ ...rdEdgeSearchQueryFilterValidationTest.java | 75 +++++ ...DashboardEdgeTypeFilterValidationTest.java | 57 ++++ ...shboardEntityListFilterValidationTest.java | 57 ++++ ...shboardEntityNameFilterValidationTest.java | 57 ++++ ...shboardEntityTypeFilterValidationTest.java | 47 +++ ...tyViewSearchQueryFilterValidationTest.java | 75 +++++ ...ardEntityViewTypeFilterValidationTest.java | 57 ++++ ...ardRelationsQueryFilterValidationTest.java | 98 ++++++ ...boardSingleEntityFilterValidationTest.java | 55 +++ ...hboardStateEntityFilterValidationTest.java | 47 +++ .../DashboardViolationAggregationTest.java | 38 +++ 42 files changed, 2178 insertions(+), 59 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/DashboardConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/EntityAlias.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAliasFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardApiUsageStateFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetSearchQueryFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetTypeFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceSearchQueryFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceTypeFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeSearchQueryFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeTypeFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityListFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityNameFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityTypeFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewSearchQueryFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewTypeFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardSingleEntityFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStateEntityFilter.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRoot.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/AbstractDashboardDataValidatorTest.java delete mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/DashboardDataValidatorTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardApiUsageStateFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardAssetSearchQueryFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardAssetTypeFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardConfigurationStructureTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardDeviceSearchQueryFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardDeviceTypeFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEdgeSearchQueryFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEdgeTypeFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityListFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityNameFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityTypeFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityViewSearchQueryFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityViewTypeFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardRelationsQueryFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardSingleEntityFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardStateEntityFilterValidationTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardViolationAggregationTest.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/DashboardConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/DashboardConfig.java new file mode 100644 index 0000000000..5a6b093510 --- /dev/null +++ b/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 entityAliases; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/EntityAlias.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/EntityAlias.java new file mode 100644 index 0000000000..1adbeb3c80 --- /dev/null +++ b/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; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAliasFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAliasFilter.java new file mode 100644 index 0000000000..728e235131 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAliasFilter.java @@ -0,0 +1,44 @@ +/** + * 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; + +@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 interface DashboardAliasFilter {} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardApiUsageStateFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardApiUsageStateFilter.java new file mode 100644 index 0000000000..5555752f3f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardApiUsageStateFilter.java @@ -0,0 +1,26 @@ +/** + * 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 org.thingsboard.server.common.data.id.CustomerId; + +@Data +public class DashboardApiUsageStateFilter implements DashboardAliasFilter { + + private CustomerId customerId; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetSearchQueryFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetSearchQueryFilter.java new file mode 100644 index 0000000000..7454b7b8bd --- /dev/null +++ b/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; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetTypeFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetTypeFilter.java new file mode 100644 index 0000000000..45d662e56c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetTypeFilter.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.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Data +public class DashboardAssetTypeFilter implements DashboardAliasFilter { + + @NotEmpty + private List<@NotBlank String> assetTypes; + + private String assetNameFilter; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceSearchQueryFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceSearchQueryFilter.java new file mode 100644 index 0000000000..9d9e3a6019 --- /dev/null +++ b/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; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceTypeFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceTypeFilter.java new file mode 100644 index 0000000000..7d0244fa0b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceTypeFilter.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.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Data +public class DashboardDeviceTypeFilter implements DashboardAliasFilter { + + @NotEmpty + private List<@NotBlank String> deviceTypes; + + private String deviceNameFilter; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeSearchQueryFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeSearchQueryFilter.java new file mode 100644 index 0000000000..a30b959f48 --- /dev/null +++ b/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; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeTypeFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeTypeFilter.java new file mode 100644 index 0000000000..23c5e88dff --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeTypeFilter.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.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Data +public class DashboardEdgeTypeFilter implements DashboardAliasFilter { + + @NotEmpty + private List<@NotBlank String> edgeTypes; + + private String edgeNameFilter; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityListFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityListFilter.java new file mode 100644 index 0000000000..bd0b0dfba4 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityListFilter.java @@ -0,0 +1,35 @@ +/** + * 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 org.thingsboard.server.common.data.EntityType; + +import java.util.List; +import java.util.UUID; + +@Data +public class DashboardEntityListFilter implements DashboardAliasFilter { + + @NotNull + private EntityType entityType; + + @NotEmpty + private List<@NotNull UUID> entityList; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityNameFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityNameFilter.java new file mode 100644 index 0000000000..ed1bb07a19 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityNameFilter.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.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.Data; +import org.thingsboard.server.common.data.EntityType; + +@Data +public class DashboardEntityNameFilter implements DashboardAliasFilter { + + @NotNull + private EntityType entityType; + + @NotBlank + private String entityNameFilter; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java new file mode 100644 index 0000000000..cf9f5c21c8 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java @@ -0,0 +1,46 @@ +/** + * 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 org.thingsboard.server.common.data.query.AliasEntityId; +import org.thingsboard.server.common.data.relation.EntitySearchDirection; + +@DashboardStatefulRoot +@Data +public abstract class DashboardEntitySearchQueryFilter 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; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityTypeFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityTypeFilter.java new file mode 100644 index 0000000000..4cb2262ef8 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityTypeFilter.java @@ -0,0 +1,28 @@ +/** + * 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 org.thingsboard.server.common.data.EntityType; + +@Data +public class DashboardEntityTypeFilter implements DashboardAliasFilter { + + @NotNull + private EntityType entityType; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewSearchQueryFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewSearchQueryFilter.java new file mode 100644 index 0000000000..c28973b6f2 --- /dev/null +++ b/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; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewTypeFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewTypeFilter.java new file mode 100644 index 0000000000..f5f116b00a --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewTypeFilter.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.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import lombok.Data; + +import java.util.List; + +@Data +public class DashboardEntityViewTypeFilter implements DashboardAliasFilter { + + @NotEmpty + private List<@NotBlank String> entityViewTypes; + + private String entityViewNameFilter; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java new file mode 100644 index 0000000000..3bb47d466f --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java @@ -0,0 +1,51 @@ +/** + * 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 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; + +@DashboardStatefulRoot +@Data +public class DashboardRelationsQueryFilter 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 filters; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardSingleEntityFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardSingleEntityFilter.java new file mode 100644 index 0000000000..c827672718 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardSingleEntityFilter.java @@ -0,0 +1,28 @@ +/** + * 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 org.thingsboard.server.common.data.query.AliasEntityId; + +@Data +public class DashboardSingleEntityFilter implements DashboardAliasFilter { + + @NotNull + private AliasEntityId singleEntity; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStateEntityFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStateEntityFilter.java new file mode 100644 index 0000000000..da212e6873 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStateEntityFilter.java @@ -0,0 +1,28 @@ +/** + * 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 org.thingsboard.server.common.data.query.AliasEntityId; + +@Data +public class DashboardStateEntityFilter implements DashboardAliasFilter { + + private String stateEntityParamName; + + private AliasEntityId defaultStateEntity; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRoot.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRoot.java new file mode 100644 index 0000000000..d2052db369 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRoot.java @@ -0,0 +1,58 @@ +/** + * 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.Constraint; +import jakarta.validation.ConstraintValidator; +import jakarta.validation.ConstraintValidatorContext; +import jakarta.validation.Payload; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Constraint(validatedBy = DashboardStatefulRoot.Validator.class) +public @interface DashboardStatefulRoot { + + String message() default "invalid root entity configuration"; + + Class[] groups() default {}; + + Class[] payload() default {}; + + class Validator implements ConstraintValidator { + + @Override + public boolean isValid(DashboardStatefulRootFilter f, ConstraintValidatorContext ctx) { + if (f == null) { + return true; + } + if (f.getRootEntity() == null && !f.isRootStateEntity()) { + ctx.disableDefaultConstraintViolation(); + ctx.buildConstraintViolationWithTemplate("must not be null when 'rootStateEntity' is false") + .addPropertyNode("rootEntity") + .addConstraintViolation(); + return false; + } + return true; + } + + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java new file mode 100644 index 0000000000..9161c5c8be --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java @@ -0,0 +1,26 @@ +/** + * 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 org.thingsboard.server.common.data.query.AliasEntityId; + +public interface DashboardStatefulRootFilter extends DashboardAliasFilter { + + AliasEntityId getRootEntity(); + + boolean isRootStateEntity(); + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java index 5df895ac80..00b240dc01 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/ConstraintValidator.java @@ -61,6 +61,10 @@ public class ConstraintValidator { } } + public static Set> getViolations(T data) { + return fieldsValidator.validate(data); + } + public static String getErrorMessage(Collection> constraintsViolations) { return constraintsViolations.stream() .map(ConstraintValidator::getErrorMessage) diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java index 3236b75b2c..6f43b02615 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java @@ -15,20 +15,32 @@ */ 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 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 { - @Autowired - private TenantService tenantService; + private final TenantService tenantService; @Override protected void validateCreate(TenantId tenantId, Dashboard data) { @@ -45,5 +57,67 @@ public class DashboardDataValidator extends DataValidator { 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> 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 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 : ""; + if (aliasKey != null) { + EntityAlias alias = cfg.getEntityAliases().get(aliasKey); + String aliasName = alias != null && alias.getAlias() != null && !alias.getAlias().isBlank() + ? alias.getAlias() : aliasKey.toString(); + return "alias '" + aliasName + "' field '" + fieldName + "'" + elementInfo + " " + 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!"); + } + } + } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/AbstractDashboardDataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/AbstractDashboardDataValidatorTest.java new file mode 100644 index 0000000000..ac912be352 --- /dev/null +++ b/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); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DashboardDataValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/DashboardDataValidatorTest.java deleted file mode 100644 index 3029027f37..0000000000 --- a/dao/src/test/java/org/thingsboard/server/dao/service/validator/DashboardDataValidatorTest.java +++ /dev/null @@ -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()); - } - -} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardApiUsageStateFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardApiUsageStateFilterValidationTest.java new file mode 100644 index 0000000000..16dc74d03a --- /dev/null +++ b/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(); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardAssetSearchQueryFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardAssetSearchQueryFilterValidationTest.java new file mode 100644 index 0000000000..70f81d63b4 --- /dev/null +++ b/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"); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardAssetTypeFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardAssetTypeFilterValidationTest.java new file mode 100644 index 0000000000..53a9b7bfb0 --- /dev/null +++ b/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"); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardConfigurationStructureTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardConfigurationStructureTest.java new file mode 100644 index 0000000000..e0cda63061 --- /dev/null +++ b/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:"); + } + + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardDeviceSearchQueryFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardDeviceSearchQueryFilterValidationTest.java new file mode 100644 index 0000000000..4bb2629166 --- /dev/null +++ b/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"); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardDeviceTypeFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardDeviceTypeFilterValidationTest.java new file mode 100644 index 0000000000..0552a9ee44 --- /dev/null +++ b/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"); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEdgeSearchQueryFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEdgeSearchQueryFilterValidationTest.java new file mode 100644 index 0000000000..e7c8928195 --- /dev/null +++ b/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"); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEdgeTypeFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEdgeTypeFilterValidationTest.java new file mode 100644 index 0000000000..a7a10366c6 --- /dev/null +++ b/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"); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityListFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityListFilterValidationTest.java new file mode 100644 index 0000000000..9457173cf3 --- /dev/null +++ b/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"); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityNameFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityNameFilterValidationTest.java new file mode 100644 index 0000000000..948ac3aa23 --- /dev/null +++ b/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"); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityTypeFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityTypeFilterValidationTest.java new file mode 100644 index 0000000000..7fe46f6036 --- /dev/null +++ b/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"); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityViewSearchQueryFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityViewSearchQueryFilterValidationTest.java new file mode 100644 index 0000000000..e92a83e1ec --- /dev/null +++ b/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"); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityViewTypeFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardEntityViewTypeFilterValidationTest.java new file mode 100644 index 0000000000..818626b0db --- /dev/null +++ b/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"); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardRelationsQueryFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardRelationsQueryFilterValidationTest.java new file mode 100644 index 0000000000..ce0b7e8053 --- /dev/null +++ b/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' field 'rootEntity' must not be null when 'rootStateEntity' is false"); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardSingleEntityFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardSingleEntityFilterValidationTest.java new file mode 100644 index 0000000000..8d655333e3 --- /dev/null +++ b/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"); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardStateEntityFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardStateEntityFilterValidationTest.java new file mode 100644 index 0000000000..2c6336b50c --- /dev/null +++ b/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(); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardViolationAggregationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardViolationAggregationTest.java new file mode 100644 index 0000000000..a0cbec4630 --- /dev/null +++ b/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"); + } + +} From 88d08e185ce7c70e72bb2da61d290a67c4d48ab5 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 22 May 2026 14:39:21 +0300 Subject: [PATCH 2/4] test: add missing type and entityType to dashboard alias filter in VC test --- .../thingsboard/server/service/sync/vc/VersionControlTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java b/application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java index 7496eaf945..db0ed47c56 100644 --- a/application/src/test/java/org/thingsboard/server/service/sync/vc/VersionControlTest.java +++ b/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" + From b6a0ebb741dfe1d4ab11639bc5e1e737ea02bd83 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Fri, 22 May 2026 16:08:09 +0300 Subject: [PATCH 3/4] refactor: replace @DashboardStatefulRoot with @AssertTrue interface default Drops the custom class-level @Constraint annotation and its inner ConstraintValidator in favor of a Jakarta @AssertTrue default method on DashboardStatefulRootFilter. All implementers inherit the check automatically. DashboardDataValidator now strips the misleading "field ''" prefix when rendering @AssertTrue violations, so the message reads as a class-level condition. --- .../DashboardEntitySearchQueryFilter.java | 1 - .../filter/DashboardRelationsQueryFilter.java | 1 - .../filter/DashboardStatefulRoot.java | 58 ------------------- .../filter/DashboardStatefulRootFilter.java | 8 +++ .../validator/DashboardDataValidator.java | 8 +++ ...ardRelationsQueryFilterValidationTest.java | 2 +- 6 files changed, 17 insertions(+), 61 deletions(-) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRoot.java diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java index cf9f5c21c8..fcfa46cd65 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java @@ -21,7 +21,6 @@ import lombok.Data; import org.thingsboard.server.common.data.query.AliasEntityId; import org.thingsboard.server.common.data.relation.EntitySearchDirection; -@DashboardStatefulRoot @Data public abstract class DashboardEntitySearchQueryFilter implements DashboardStatefulRootFilter { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java index 3bb47d466f..e818b5c1a4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java @@ -25,7 +25,6 @@ import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; import java.util.List; -@DashboardStatefulRoot @Data public class DashboardRelationsQueryFilter implements DashboardStatefulRootFilter { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRoot.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRoot.java deleted file mode 100644 index d2052db369..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRoot.java +++ /dev/null @@ -1,58 +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.common.data.dashboard.filter; - -import jakarta.validation.Constraint; -import jakarta.validation.ConstraintValidator; -import jakarta.validation.ConstraintValidatorContext; -import jakarta.validation.Payload; - -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; - -@Target(ElementType.TYPE) -@Retention(RetentionPolicy.RUNTIME) -@Constraint(validatedBy = DashboardStatefulRoot.Validator.class) -public @interface DashboardStatefulRoot { - - String message() default "invalid root entity configuration"; - - Class[] groups() default {}; - - Class[] payload() default {}; - - class Validator implements ConstraintValidator { - - @Override - public boolean isValid(DashboardStatefulRootFilter f, ConstraintValidatorContext ctx) { - if (f == null) { - return true; - } - if (f.getRootEntity() == null && !f.isRootStateEntity()) { - ctx.disableDefaultConstraintViolation(); - ctx.buildConstraintViolationWithTemplate("must not be null when 'rootStateEntity' is false") - .addPropertyNode("rootEntity") - .addConstraintViolation(); - return false; - } - return true; - } - - } - -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java index 9161c5c8be..f1059662cd 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java @@ -15,6 +15,8 @@ */ 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 extends DashboardAliasFilter { @@ -23,4 +25,10 @@ public interface DashboardStatefulRootFilter extends DashboardAliasFilter { boolean isRootStateEntity(); + @AssertTrue(message = "must include 'rootEntity' when 'rootStateEntity' is false") + @JsonIgnore + default boolean isValidRootEntity() { + return isRootStateEntity() || getRootEntity() != null; + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java index 6f43b02615..dcf0560f09 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DashboardDataValidator.java @@ -19,6 +19,7 @@ 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; @@ -105,12 +106,19 @@ public class DashboardDataValidator extends DataValidator { } } 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(); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardRelationsQueryFilterValidationTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardRelationsQueryFilterValidationTest.java index ce0b7e8053..e1ffe299a2 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardRelationsQueryFilterValidationTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/validator/dashboard/DashboardRelationsQueryFilterValidationTest.java @@ -92,7 +92,7 @@ class DashboardRelationsQueryFilterValidationTest extends AbstractDashboardDataV assertThatThrownBy(() -> validate(dashboard)) .isInstanceOf(DataValidationException.class) - .hasMessage("Dashboard validation error: alias 'Related' field 'rootEntity' must not be null when 'rootStateEntity' is false"); + .hasMessage("Dashboard validation error: alias 'Related' must include 'rootEntity' when 'rootStateEntity' is false"); } } From af47a5b8c0f1918dd4caea19ddbc95c51f0b5f40 Mon Sep 17 00:00:00 2001 From: Dmytro Skarzhynets Date: Mon, 25 May 2026 16:37:59 +0300 Subject: [PATCH 4/4] refactor: model resolveMultiple on DashboardAliasFilter base Converts the DashboardAliasFilter marker interface into an abstract base class so the resolveMultiple flag, which the UI persists on every entityAlias filter, has a single home. Drops the extends DashboardAliasFilter from DashboardStatefulRootFilter (interface cannot extend a class); its two implementers now extend the base and implement the interface directly. Subtypes pick up resolveMultiple via inheritance and gain @EqualsAndHashCode(callSuper = true) / @ToString(callSuper = true) to keep Lombok semantics correct. --- .../data/dashboard/filter/DashboardAliasFilter.java | 8 +++++++- .../dashboard/filter/DashboardApiUsageStateFilter.java | 6 +++++- .../data/dashboard/filter/DashboardAssetTypeFilter.java | 6 +++++- .../data/dashboard/filter/DashboardDeviceTypeFilter.java | 6 +++++- .../data/dashboard/filter/DashboardEdgeTypeFilter.java | 6 +++++- .../data/dashboard/filter/DashboardEntityListFilter.java | 6 +++++- .../data/dashboard/filter/DashboardEntityNameFilter.java | 6 +++++- .../filter/DashboardEntitySearchQueryFilter.java | 6 +++++- .../data/dashboard/filter/DashboardEntityTypeFilter.java | 6 +++++- .../dashboard/filter/DashboardEntityViewTypeFilter.java | 6 +++++- .../dashboard/filter/DashboardRelationsQueryFilter.java | 6 +++++- .../dashboard/filter/DashboardSingleEntityFilter.java | 6 +++++- .../data/dashboard/filter/DashboardStateEntityFilter.java | 6 +++++- .../dashboard/filter/DashboardStatefulRootFilter.java | 2 +- 14 files changed, 68 insertions(+), 14 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAliasFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAliasFilter.java index 728e235131..16b81b9acb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAliasFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAliasFilter.java @@ -18,7 +18,9 @@ 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, @@ -41,4 +43,8 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonSubTypes.Type(value = DashboardEntityViewSearchQueryFilter.class, name = "entityViewSearchQuery"), @JsonSubTypes.Type(value = DashboardEdgeSearchQueryFilter.class, name = "edgeSearchQuery") }) -public interface DashboardAliasFilter {} +public abstract class DashboardAliasFilter { + + private boolean resolveMultiple; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardApiUsageStateFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardApiUsageStateFilter.java index 5555752f3f..a34f933b26 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardApiUsageStateFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardApiUsageStateFilter.java @@ -16,10 +16,14 @@ 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 -public class DashboardApiUsageStateFilter implements DashboardAliasFilter { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DashboardApiUsageStateFilter extends DashboardAliasFilter { private CustomerId customerId; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetTypeFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetTypeFilter.java index 45d662e56c..e0b1c1b29c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetTypeFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardAssetTypeFilter.java @@ -18,11 +18,15 @@ 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 -public class DashboardAssetTypeFilter implements DashboardAliasFilter { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DashboardAssetTypeFilter extends DashboardAliasFilter { @NotEmpty private List<@NotBlank String> assetTypes; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceTypeFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceTypeFilter.java index 7d0244fa0b..103a164db6 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceTypeFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardDeviceTypeFilter.java @@ -18,11 +18,15 @@ 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 -public class DashboardDeviceTypeFilter implements DashboardAliasFilter { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DashboardDeviceTypeFilter extends DashboardAliasFilter { @NotEmpty private List<@NotBlank String> deviceTypes; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeTypeFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeTypeFilter.java index 23c5e88dff..95923eec30 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeTypeFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEdgeTypeFilter.java @@ -18,11 +18,15 @@ 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 -public class DashboardEdgeTypeFilter implements DashboardAliasFilter { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DashboardEdgeTypeFilter extends DashboardAliasFilter { @NotEmpty private List<@NotBlank String> edgeTypes; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityListFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityListFilter.java index bd0b0dfba4..13767ea594 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityListFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityListFilter.java @@ -18,13 +18,17 @@ 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 -public class DashboardEntityListFilter implements DashboardAliasFilter { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DashboardEntityListFilter extends DashboardAliasFilter { @NotNull private EntityType entityType; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityNameFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityNameFilter.java index ed1bb07a19..4286239275 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityNameFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityNameFilter.java @@ -18,10 +18,14 @@ 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 -public class DashboardEntityNameFilter implements DashboardAliasFilter { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DashboardEntityNameFilter extends DashboardAliasFilter { @NotNull private EntityType entityType; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java index fcfa46cd65..1151b336f0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntitySearchQueryFilter.java @@ -18,11 +18,15 @@ 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 -public abstract class DashboardEntitySearchQueryFilter implements DashboardStatefulRootFilter { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public abstract class DashboardEntitySearchQueryFilter extends DashboardAliasFilter implements DashboardStatefulRootFilter { private AliasEntityId rootEntity; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityTypeFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityTypeFilter.java index 4cb2262ef8..d36f2495ff 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityTypeFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityTypeFilter.java @@ -17,10 +17,14 @@ 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 -public class DashboardEntityTypeFilter implements DashboardAliasFilter { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DashboardEntityTypeFilter extends DashboardAliasFilter { @NotNull private EntityType entityType; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewTypeFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewTypeFilter.java index f5f116b00a..0211ce75d2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewTypeFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardEntityViewTypeFilter.java @@ -18,11 +18,15 @@ 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 -public class DashboardEntityViewTypeFilter implements DashboardAliasFilter { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DashboardEntityViewTypeFilter extends DashboardAliasFilter { @NotEmpty private List<@NotBlank String> entityViewTypes; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java index e818b5c1a4..93423d7a11 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardRelationsQueryFilter.java @@ -19,6 +19,8 @@ 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; @@ -26,7 +28,9 @@ import org.thingsboard.server.common.data.relation.RelationEntityTypeFilter; import java.util.List; @Data -public class DashboardRelationsQueryFilter implements DashboardStatefulRootFilter { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DashboardRelationsQueryFilter extends DashboardAliasFilter implements DashboardStatefulRootFilter { private AliasEntityId rootEntity; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardSingleEntityFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardSingleEntityFilter.java index c827672718..b1fe8aaba2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardSingleEntityFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardSingleEntityFilter.java @@ -17,10 +17,14 @@ 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 -public class DashboardSingleEntityFilter implements DashboardAliasFilter { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DashboardSingleEntityFilter extends DashboardAliasFilter { @NotNull private AliasEntityId singleEntity; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStateEntityFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStateEntityFilter.java index da212e6873..b6cbe08973 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStateEntityFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStateEntityFilter.java @@ -16,10 +16,14 @@ 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 -public class DashboardStateEntityFilter implements DashboardAliasFilter { +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) +public class DashboardStateEntityFilter extends DashboardAliasFilter { private String stateEntityParamName; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java index f1059662cd..709ee18c76 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/dashboard/filter/DashboardStatefulRootFilter.java @@ -19,7 +19,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import jakarta.validation.constraints.AssertTrue; import org.thingsboard.server.common.data.query.AliasEntityId; -public interface DashboardStatefulRootFilter extends DashboardAliasFilter { +public interface DashboardStatefulRootFilter { AliasEntityId getRootEntity();