Browse Source

merge with develop/3.5.2

pull/8825/head
YevhenBondarenko 3 years ago
parent
commit
ada9ff7b4e
  1. 5
      application/src/main/data/json/system/widget_bundles/alarm_widgets.json
  2. 29
      application/src/main/data/json/system/widget_bundles/analogue_gauges.json
  3. 70
      application/src/main/data/json/system/widget_bundles/cards.json
  4. 45
      application/src/main/data/json/system/widget_bundles/charts.json
  5. 56
      application/src/main/data/json/system/widget_bundles/control_widgets.json
  6. 9
      application/src/main/data/json/system/widget_bundles/date.json
  7. 69
      application/src/main/data/json/system/widget_bundles/digital_gauges.json
  8. 9
      application/src/main/data/json/system/widget_bundles/edge_widgets.json
  9. 14
      application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json
  10. 19
      application/src/main/data/json/system/widget_bundles/gateway_widgets.json
  11. 24
      application/src/main/data/json/system/widget_bundles/gpio_widgets.json
  12. 25
      application/src/main/data/json/system/widget_bundles/home_page_widgets.json
  13. 134
      application/src/main/data/json/system/widget_bundles/input_widgets.json
  14. 51
      application/src/main/data/json/system/widget_bundles/maps.json
  15. 14
      application/src/main/data/json/system/widget_bundles/navigation_widgets.json
  16. 15
      application/src/main/data/upgrade/3.5.1/schema_update.sql
  17. 52
      application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java
  18. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java
  19. 4
      application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetsBundleImportService.java
  20. 12
      application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java
  21. 2
      application/src/test/java/org/thingsboard/server/edge/WidgetEdgeTest.java
  22. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java
  23. 2
      common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java
  24. 2
      common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java
  25. 12
      common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java
  26. 2
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java
  27. 4
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java
  28. 6
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java
  29. 2
      common/edge-api/src/main/proto/edge.proto
  30. 36
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  31. 6
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java
  32. 55
      common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/MqttTransportHandlerTest.java
  33. 5
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java
  34. 14
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java
  35. 5
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  36. 16
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java
  37. 24
      dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java
  38. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java
  39. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java
  40. 7
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java
  41. 37
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
  42. 6
      dao/src/main/resources/sql/schema-entities.sql
  43. 10
      dao/src/test/java/org/thingsboard/server/dao/service/WidgetTypeServiceTest.java
  44. 6
      dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java
  45. 309
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java
  46. 50
      ui-ngx/src/app/core/http/widget.service.ts
  47. 35
      ui-ngx/src/app/core/services/dashboard-utils.service.ts
  48. 4
      ui-ngx/src/app/modules/dashboard/dashboard-pages.routing.module.ts
  49. 8
      ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts
  50. 6
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  51. 6
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts
  52. 7
      ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts
  53. 60
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  54. 17
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  55. 7
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  56. 107
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  57. 2
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts
  58. 10
      ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts
  59. 11
      ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts
  60. 25
      ui-ngx/src/app/shared/models/dashboard.models.ts
  61. 55
      ui-ngx/src/app/shared/models/widget.models.ts

5
application/src/main/data/json/system/widget_bundles/alarm_widgets.json

File diff suppressed because one or more lines are too long

29
application/src/main/data/json/system/widget_bundles/analogue_gauges.json

File diff suppressed because one or more lines are too long

70
application/src/main/data/json/system/widget_bundles/cards.json

File diff suppressed because one or more lines are too long

45
application/src/main/data/json/system/widget_bundles/charts.json

File diff suppressed because one or more lines are too long

56
application/src/main/data/json/system/widget_bundles/control_widgets.json

File diff suppressed because one or more lines are too long

9
application/src/main/data/json/system/widget_bundles/date.json

@ -3,11 +3,12 @@
"alias": "date", "alias": "date",
"title": "Date", "title": "Date",
"image": "data:image/png;base64,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", "image": "data:image/png;base64,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",
"description": "Contains widgets to change the data range for other widgets on the dashboard." "description": "Contains widgets to change the data range for other widgets on the dashboard.",
"externalId": null,
"name": "Date"
}, },
"widgetTypes": [ "widgetTypes": [
{ {
"alias": "date_range_navigator",
"name": "Date-range-navigator", "name": "Date-range-navigator",
"image": "data:image/png;base64,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", "image": "data:image/png;base64,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",
"description": "Allows to change the data range for other widgets on the dashboard.", "description": "Allows to change the data range for other widgets on the dashboard.",
@ -23,7 +24,9 @@
"dataKeySettingsSchema": "{}\n", "dataKeySettingsSchema": "{}\n",
"settingsDirective": "tb-date-range-navigator-widget-settings", "settingsDirective": "tb-date-range-navigator-widget-settings",
"defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"defaultInterval\":\"week\",\"stepSize\":\"day\"},\"title\":\"Date-range-navigator\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}" "defaultConfig": "{\"datasources\":[{\"type\":\"static\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"defaultInterval\":\"week\",\"stepSize\":\"day\"},\"title\":\"Date-range-navigator\",\"dropShadow\":true,\"enableFullscreen\":true,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"showLegend\":false,\"actions\":{}}"
} },
"fqn": "date.date_range_navigator",
"deprecated": false
} }
] ]
} }

69
application/src/main/data/json/system/widget_bundles/digital_gauges.json

File diff suppressed because one or more lines are too long

9
application/src/main/data/json/system/widget_bundles/edge_widgets.json

File diff suppressed because one or more lines are too long

14
application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json

File diff suppressed because one or more lines are too long

19
application/src/main/data/json/system/widget_bundles/gateway_widgets.json

File diff suppressed because one or more lines are too long

24
application/src/main/data/json/system/widget_bundles/gpio_widgets.json

File diff suppressed because one or more lines are too long

25
application/src/main/data/json/system/widget_bundles/home_page_widgets.json

File diff suppressed because one or more lines are too long

134
application/src/main/data/json/system/widget_bundles/input_widgets.json

File diff suppressed because one or more lines are too long

51
application/src/main/data/json/system/widget_bundles/maps.json

File diff suppressed because one or more lines are too long

14
application/src/main/data/json/system/widget_bundles/navigation_widgets.json

File diff suppressed because one or more lines are too long

15
application/src/main/data/upgrade/3.5.1/schema_update.sql

@ -134,3 +134,18 @@ CREATE TABLE IF NOT EXISTS alarm_types (
); );
INSERT INTO alarm_types (tenant_id, type) SELECT DISTINCT tenant_id, type FROM alarm ON CONFLICT (tenant_id, type) DO NOTHING; INSERT INTO alarm_types (tenant_id, type) SELECT DISTINCT tenant_id, type FROM alarm ON CONFLICT (tenant_id, type) DO NOTHING;
ALTER TABLE widget_type
ADD COLUMN IF NOT EXISTS fqn varchar(512);
ALTER TABLE widget_type
ADD COLUMN IF NOT EXISTS deprecated boolean NOT NULL DEFAULT false;
DO
$$
BEGIN
IF NOT EXISTS(SELECT 1 FROM pg_constraint WHERE conname = 'uq_widget_type_fqn') THEN
UPDATE widget_type SET fqn = concat(widget_type.bundle_alias, '.', widget_type.alias);
ALTER TABLE widget_type ADD CONSTRAINT uq_widget_type_fqn UNIQUE (tenant_id, fqn);
ALTER TABLE widget_type DROP COLUMN IF EXISTS alias;
END IF;
END;
$$;

52
application/src/main/java/org/thingsboard/server/controller/WidgetTypeController.java

@ -28,6 +28,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetTypeId;
@ -206,10 +207,59 @@ public class WidgetTypeController extends AutoCommitController {
} else { } else {
tenantId = getCurrentUser().getTenantId(); tenantId = getCurrentUser().getTenantId();
} }
WidgetType widgetType = widgetTypeService.findWidgetTypeByTenantIdBundleAliasAndAlias(tenantId, bundleAlias, alias); WidgetType widgetType = widgetTypeService.findWidgetTypeByTenantIdAndFqn(tenantId, bundleAlias + "." + alias);
checkNotNull(widgetType); checkNotNull(widgetType);
accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, Operation.READ, widgetType.getId(), widgetType); accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, Operation.READ, widgetType.getId(), widgetType);
return widgetType; return widgetType;
} }
@ApiOperation(value = "Get Widget Type by fqn (getWidgetTypeByFqn)",
notes = "Get the Widget Type by FQN. " + WIDGET_TYPE_DESCRIPTION + AVAILABLE_FOR_ANY_AUTHORIZED_USER)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/widgetType", params = {"fqn"}, method = RequestMethod.GET)
@ResponseBody
public WidgetType getWidgetTypeByFqn(
@ApiParam(value = "Widget Type fqn", required = true)
@RequestParam String fqn) throws ThingsboardException {
String[] parts = fqn.split("\\.");
String scopeQualifier = parts.length > 0 ? parts[0] : null;
if (parts.length < 2 || (!scopeQualifier.equals("system") && !scopeQualifier.equals("tenant"))) {
throw new ThingsboardException("Invalid fqn!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
TenantId tenantId;
if ("system".equals(scopeQualifier)) {
tenantId = TenantId.fromUUID(ModelConstants.NULL_UUID);
} else {
tenantId = getCurrentUser().getTenantId();
}
String typeFqn = fqn.substring(scopeQualifier.length() + 1);
WidgetType widgetType = widgetTypeService.findWidgetTypeByTenantIdAndFqn(tenantId, typeFqn);
checkNotNull(widgetType);
accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, Operation.READ, widgetType.getId(), widgetType);
return widgetType;
}
@ApiOperation(value = "Set widget type deprecated (setWidgetTypeDeprecated)",
notes = "Set Widget Type deprecated flag. Referencing non-existing Widget Type Id will cause an error." + SYSTEM_OR_TENANT_AUTHORITY_PARAGRAPH)
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/widgetType/{widgetTypeId}/deprecate/{deprecated}", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)
public void setWidgetTypeDeprecated(
@ApiParam(value = WIDGET_TYPE_ID_PARAM_DESCRIPTION, required = true)
@PathVariable("widgetTypeId") String strWidgetTypeId,
@PathVariable("deprecated") boolean deprecated) throws Exception {
checkParameter("widgetTypeId", strWidgetTypeId);
var currentUser = getCurrentUser();
WidgetTypeId widgetTypeId = new WidgetTypeId(toUUID(strWidgetTypeId));
WidgetTypeDetails wtd = checkWidgetTypeId(widgetTypeId, Operation.WRITE);
widgetTypeService.setWidgetTypeDeprecated(currentUser.getTenantId(), widgetTypeId, deprecated);
if (wtd != null && !Authority.SYS_ADMIN.equals(currentUser.getAuthority())) {
WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleByTenantIdAndAlias(wtd.getTenantId(), wtd.getBundleAlias());
if (widgetsBundle != null) {
autoCommit(currentUser, widgetsBundle.getId());
}
}
}
} }

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/WidgetTypeMsgConstructor.java

@ -36,8 +36,8 @@ public class WidgetTypeMsgConstructor {
if (widgetTypeDetails.getBundleAlias() != null) { if (widgetTypeDetails.getBundleAlias() != null) {
builder.setBundleAlias(widgetTypeDetails.getBundleAlias()); builder.setBundleAlias(widgetTypeDetails.getBundleAlias());
} }
if (widgetTypeDetails.getAlias() != null) { if (widgetTypeDetails.getFqn() != null) {
builder.setAlias(widgetTypeDetails.getAlias()); builder.setFqn(widgetTypeDetails.getFqn());
} }
if (widgetTypeDetails.getName() != null) { if (widgetTypeDetails.getName() != null) {
builder.setName(widgetTypeDetails.getName()); builder.setName(widgetTypeDetails.getName());

4
application/src/main/java/org/thingsboard/server/service/sync/ie/importing/impl/WidgetsBundleImportService.java

@ -63,10 +63,10 @@ public class WidgetsBundleImportService extends BaseEntityImportService<WidgetsB
} }
} else { } else {
Map<String, WidgetTypeInfo> existingWidgets = widgetTypeService.findWidgetTypesInfosByTenantIdAndBundleAlias(ctx.getTenantId(), savedWidgetsBundle.getAlias()).stream() Map<String, WidgetTypeInfo> existingWidgets = widgetTypeService.findWidgetTypesInfosByTenantIdAndBundleAlias(ctx.getTenantId(), savedWidgetsBundle.getAlias()).stream()
.collect(Collectors.toMap(BaseWidgetType::getAlias, w -> w)); .collect(Collectors.toMap(BaseWidgetType::getFqn, w -> w));
for (WidgetTypeDetails widget : exportData.getWidgets()) { for (WidgetTypeDetails widget : exportData.getWidgets()) {
WidgetTypeInfo existingWidget; WidgetTypeInfo existingWidget;
if ((existingWidget = existingWidgets.remove(widget.getAlias())) != null) { if ((existingWidget = existingWidgets.remove(widget.getFqn())) != null) {
widget.setId(existingWidget.getId()); widget.setId(existingWidget.getId());
widget.setCreatedTime(existingWidget.getCreatedTime()); widget.setCreatedTime(existingWidget.getCreatedTime());
} else { } else {

12
application/src/test/java/org/thingsboard/server/controller/WidgetTypeControllerTest.java

@ -87,7 +87,7 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
Assert.assertNotNull(savedWidgetType); Assert.assertNotNull(savedWidgetType);
Assert.assertNotNull(savedWidgetType.getId()); Assert.assertNotNull(savedWidgetType.getId());
Assert.assertNotNull(savedWidgetType.getAlias()); Assert.assertNotNull(savedWidgetType.getFqn());
Assert.assertTrue(savedWidgetType.getCreatedTime() > 0); Assert.assertTrue(savedWidgetType.getCreatedTime() > 0);
Assert.assertEquals(savedTenant.getId(), savedWidgetType.getTenantId()); Assert.assertEquals(savedTenant.getId(), savedWidgetType.getTenantId());
Assert.assertEquals(widgetType.getName(), savedWidgetType.getName()); Assert.assertEquals(widgetType.getName(), savedWidgetType.getName());
@ -199,16 +199,16 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
} }
@Test @Test
public void testUpdateWidgetTypeAlias() throws Exception { public void testUpdateWidgetTypeFqn() throws Exception {
WidgetTypeDetails widgetType = new WidgetTypeDetails(); WidgetTypeDetails widgetType = new WidgetTypeDetails();
widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
widgetType.setName("Widget Type"); widgetType.setName("Widget Type");
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class)); widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class); WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class);
savedWidgetType.setAlias("some_alias"); savedWidgetType.setFqn("some_fqn");
doPost("/api/widgetType", savedWidgetType) doPost("/api/widgetType", savedWidgetType)
.andExpect(status().isBadRequest()) .andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Update of widget type alias is prohibited"))); .andExpect(statusReason(containsString("Update of widget type fqn is prohibited")));
} }
@ -239,8 +239,8 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
widgetType.setName("Widget Type"); widgetType.setName("Widget Type");
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class)); widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class); WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class);
WidgetType foundWidgetType = doGet("/api/widgetType?isSystem={isSystem}&bundleAlias={bundleAlias}&alias={alias}", WidgetType foundWidgetType = doGet("/api/widgetType?fqn={fqn}",
WidgetType.class, false, savedWidgetsBundle.getAlias(), savedWidgetType.getAlias()); WidgetType.class, "tenant."+savedWidgetType.getFqn());
Assert.assertNotNull(foundWidgetType); Assert.assertNotNull(foundWidgetType);
Assert.assertEquals(new WidgetType(savedWidgetType), foundWidgetType); Assert.assertEquals(new WidgetType(savedWidgetType), foundWidgetType);
} }

2
application/src/test/java/org/thingsboard/server/edge/WidgetEdgeTest.java

@ -66,7 +66,7 @@ public class WidgetEdgeTest extends AbstractEdgeTest {
Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, widgetTypeUpdateMsg.getMsgType()); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, widgetTypeUpdateMsg.getMsgType());
Assert.assertEquals(savedWidgetType.getUuidId().getMostSignificantBits(), widgetTypeUpdateMsg.getIdMSB()); Assert.assertEquals(savedWidgetType.getUuidId().getMostSignificantBits(), widgetTypeUpdateMsg.getIdMSB());
Assert.assertEquals(savedWidgetType.getUuidId().getLeastSignificantBits(), widgetTypeUpdateMsg.getIdLSB()); Assert.assertEquals(savedWidgetType.getUuidId().getLeastSignificantBits(), widgetTypeUpdateMsg.getIdLSB());
Assert.assertEquals(savedWidgetType.getAlias(), widgetTypeUpdateMsg.getAlias()); Assert.assertEquals(savedWidgetType.getFqn(), widgetTypeUpdateMsg.getFqn());
Assert.assertEquals(savedWidgetType.getName(), widgetTypeUpdateMsg.getName()); Assert.assertEquals(savedWidgetType.getName(), widgetTypeUpdateMsg.getName());
Assert.assertEquals(JacksonUtil.toJsonNode(widgetTypeUpdateMsg.getDescriptorJson()), savedWidgetType.getDescriptor()); Assert.assertEquals(JacksonUtil.toJsonNode(widgetTypeUpdateMsg.getDescriptorJson()), savedWidgetType.getDescriptor());

4
common/dao-api/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeService.java

@ -35,6 +35,8 @@ public interface WidgetTypeService extends EntityDaoService {
void deleteWidgetType(TenantId tenantId, WidgetTypeId widgetTypeId); void deleteWidgetType(TenantId tenantId, WidgetTypeId widgetTypeId);
void setWidgetTypeDeprecated(TenantId tenantId, WidgetTypeId widgetTypeId, boolean deprecated);
List<WidgetType> findWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias); List<WidgetType> findWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias);
List<WidgetTypeDetails> findWidgetTypesDetailsByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias); List<WidgetTypeDetails> findWidgetTypesDetailsByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias);
@ -43,7 +45,7 @@ public interface WidgetTypeService extends EntityDaoService {
List<WidgetTypeDetails> findWidgetTypesInfosByTenantIdAndResourceId(TenantId tenantId, TbResourceId tbResourceId); List<WidgetTypeDetails> findWidgetTypesInfosByTenantIdAndResourceId(TenantId tenantId, TbResourceId tbResourceId);
WidgetType findWidgetTypeByTenantIdBundleAliasAndAlias(TenantId tenantId, String bundleAlias, String alias); WidgetType findWidgetTypeByTenantIdAndFqn(TenantId tenantId, String fqn);
void deleteWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias); void deleteWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias);

2
common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java

@ -124,6 +124,8 @@ public class DataConstants {
public static final String LAST_CONNECTED_GATEWAY = "lastConnectedGateway"; public static final String LAST_CONNECTED_GATEWAY = "lastConnectedGateway";
public static final String MQTT_TOPIC = "mqttTopic";
public static final String MAIN_QUEUE_NAME = "Main"; public static final String MAIN_QUEUE_NAME = "Main";
public static final String MAIN_QUEUE_TOPIC = "tb_rule_engine.main"; public static final String MAIN_QUEUE_TOPIC = "tb_rule_engine.main";
public static final String HP_QUEUE_NAME = "HighPriority"; public static final String HP_QUEUE_NAME = "HighPriority";

2
common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/WidgetsBundleExportData.java

@ -35,7 +35,7 @@ public class WidgetsBundleExportData extends EntityExportData<WidgetsBundle> {
@Override @Override
public EntityExportData<WidgetsBundle> sort() { public EntityExportData<WidgetsBundle> sort() {
super.sort(); super.sort();
widgets.sort(Comparator.comparing(BaseWidgetType::getAlias)); widgets.sort(Comparator.comparing(BaseWidgetType::getFqn));
return this; return this;
} }

12
common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java

@ -37,14 +37,17 @@ public class BaseWidgetType extends BaseData<WidgetTypeId> implements HasName, H
@ApiModelProperty(position = 4, value = "Reference to widget bundle", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 4, value = "Reference to widget bundle", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String bundleAlias; private String bundleAlias;
@NoXss @NoXss
@Length(fieldName = "alias") @Length(fieldName = "fqn")
@ApiModelProperty(position = 5, value = "Unique alias that is used in dashboards as a reference widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 5, value = "Unique FQN that is used in dashboards as a reference widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String alias; private String fqn;
@NoXss @NoXss
@Length(fieldName = "name") @Length(fieldName = "name")
@ApiModelProperty(position = 6, value = "Widget name used in search and UI", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 6, value = "Widget name used in search and UI", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String name; private String name;
@ApiModelProperty(position = 7, value = "Whether widget type is deprecated.", example = "true")
private boolean deprecated;
public BaseWidgetType() { public BaseWidgetType() {
super(); super();
} }
@ -57,8 +60,9 @@ public class BaseWidgetType extends BaseData<WidgetTypeId> implements HasName, H
super(widgetType); super(widgetType);
this.tenantId = widgetType.getTenantId(); this.tenantId = widgetType.getTenantId();
this.bundleAlias = widgetType.getBundleAlias(); this.bundleAlias = widgetType.getBundleAlias();
this.alias = widgetType.getAlias(); this.fqn = widgetType.getFqn();
this.name = widgetType.getName(); this.name = widgetType.getName();
this.deprecated = widgetType.isDeprecated();
} }
@ApiModelProperty(position = 1, value = "JSON object with the Widget Type Id. " + @ApiModelProperty(position = 1, value = "JSON object with the Widget Type Id. " +

2
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java

@ -23,7 +23,7 @@ import org.thingsboard.server.common.data.id.WidgetTypeId;
@Data @Data
public class WidgetType extends BaseWidgetType { public class WidgetType extends BaseWidgetType {
@ApiModelProperty(position = 7, value = "Complex JSON object that describes the widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 8, value = "Complex JSON object that describes the widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private transient JsonNode descriptor; private transient JsonNode descriptor;
public WidgetType() { public WidgetType() {

4
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java

@ -27,11 +27,11 @@ import org.thingsboard.server.common.data.validation.NoXss;
public class WidgetTypeDetails extends WidgetType { public class WidgetTypeDetails extends WidgetType {
@Length(fieldName = "image", max = 1000000) @Length(fieldName = "image", max = 1000000)
@ApiModelProperty(position = 8, value = "Base64 encoded thumbnail", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 9, value = "Base64 encoded thumbnail", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String image; private String image;
@NoXss @NoXss
@Length(fieldName = "description") @Length(fieldName = "description")
@ApiModelProperty(position = 9, value = "Description of the widget", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 10, value = "Description of the widget", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String description; private String description;
public WidgetTypeDetails() { public WidgetTypeDetails() {

6
common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java

@ -23,13 +23,13 @@ import org.thingsboard.server.common.data.validation.NoXss;
@Data @Data
public class WidgetTypeInfo extends BaseWidgetType { public class WidgetTypeInfo extends BaseWidgetType {
@ApiModelProperty(position = 7, value = "Base64 encoded widget thumbnail", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 8, value = "Base64 encoded widget thumbnail", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String image; private String image;
@NoXss @NoXss
@ApiModelProperty(position = 7, value = "Description of the widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 9, value = "Description of the widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String description; private String description;
@NoXss @NoXss
@ApiModelProperty(position = 8, value = "Type of the widget (timeseries, latest, control, alarm or static)", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @ApiModelProperty(position = 10, value = "Type of the widget (timeseries, latest, control, alarm or static)", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String widgetType; private String widgetType;
public WidgetTypeInfo() { public WidgetTypeInfo() {

2
common/edge-api/src/main/proto/edge.proto

@ -362,7 +362,7 @@ message WidgetTypeUpdateMsg {
int64 idMSB = 2; int64 idMSB = 2;
int64 idLSB = 3; int64 idLSB = 3;
optional string bundleAlias = 4; optional string bundleAlias = 4;
optional string alias = 5; optional string fqn = 5;
optional string name = 6; optional string name = 6;
optional string descriptorJson = 7; optional string descriptorJson = 7;
bool isSystem = 8; bool isSystem = 8;

36
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -59,6 +59,7 @@ import org.thingsboard.server.common.data.id.OtaPackageId;
import org.thingsboard.server.common.data.ota.OtaPackageType; import org.thingsboard.server.common.data.ota.OtaPackageType;
import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.msg.EncryptionUtil; import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.msg.tools.TbRateLimitsException;
import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.SessionMsgListener;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
@ -136,6 +137,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
private static final MqttQoS MAX_SUPPORTED_QOS_LVL = AT_LEAST_ONCE; private static final MqttQoS MAX_SUPPORTED_QOS_LVL = AT_LEAST_ONCE;
private final UUID sessionId; private final UUID sessionId;
protected final MqttTransportContext context; protected final MqttTransportContext context;
private final TransportService transportService; private final TransportService transportService;
private final SchedulerComponent scheduler; private final SchedulerComponent scheduler;
@ -442,10 +444,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor(); MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor();
if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) { if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) {
TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getMetadata(deviceSessionCtx, topicName),
getPubAckCallback(ctx, msgId, postAttributeMsg));
} else if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) { } else if (deviceSessionCtx.isDeviceTelemetryTopic(topicName)) {
TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getMetadata(deviceSessionCtx, topicName),
getPubAckCallback(ctx, msgId, postTelemetryMsg));
} else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) { } else if (topicName.startsWith(MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX)) {
TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX); TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), getAttributeMsg, getPubAckCallback(ctx, msgId, getAttributeMsg));
@ -466,22 +470,28 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
getOtaPackageCallback(ctx, mqttMsg, msgId, fwMatcher, OtaPackageType.SOFTWARE); getOtaPackageCallback(ctx, mqttMsg, msgId, fwMatcher, OtaPackageType.SOFTWARE);
} else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_TOPIC)) { } else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_TOPIC)) {
TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg); TransportProtos.PostTelemetryMsg postTelemetryMsg = payloadAdaptor.convertToPostTelemetry(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getMetadata(deviceSessionCtx, topicName),
getPubAckCallback(ctx, msgId, postTelemetryMsg));
} else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_JSON_TOPIC)) { } else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_JSON_TOPIC)) {
TransportProtos.PostTelemetryMsg postTelemetryMsg = context.getJsonMqttAdaptor().convertToPostTelemetry(deviceSessionCtx, mqttMsg); TransportProtos.PostTelemetryMsg postTelemetryMsg = context.getJsonMqttAdaptor().convertToPostTelemetry(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getMetadata(deviceSessionCtx, topicName),
getPubAckCallback(ctx, msgId, postTelemetryMsg));
} else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_PROTO_TOPIC)) { } else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_PROTO_TOPIC)) {
TransportProtos.PostTelemetryMsg postTelemetryMsg = context.getProtoMqttAdaptor().convertToPostTelemetry(deviceSessionCtx, mqttMsg); TransportProtos.PostTelemetryMsg postTelemetryMsg = context.getProtoMqttAdaptor().convertToPostTelemetry(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getPubAckCallback(ctx, msgId, postTelemetryMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), postTelemetryMsg, getMetadata(deviceSessionCtx, topicName),
getPubAckCallback(ctx, msgId, postTelemetryMsg));
} else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC)) { } else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_TOPIC)) {
TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg); TransportProtos.PostAttributeMsg postAttributeMsg = payloadAdaptor.convertToPostAttributes(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getMetadata(deviceSessionCtx, topicName),
getPubAckCallback(ctx, msgId, postAttributeMsg));
} else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC)) { } else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_JSON_TOPIC)) {
TransportProtos.PostAttributeMsg postAttributeMsg = context.getJsonMqttAdaptor().convertToPostAttributes(deviceSessionCtx, mqttMsg); TransportProtos.PostAttributeMsg postAttributeMsg = context.getJsonMqttAdaptor().convertToPostAttributes(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getMetadata(deviceSessionCtx, topicName),
getPubAckCallback(ctx, msgId, postAttributeMsg));
} else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC)) { } else if (topicName.equals(MqttTopics.DEVICE_ATTRIBUTES_SHORT_PROTO_TOPIC)) {
TransportProtos.PostAttributeMsg postAttributeMsg = context.getProtoMqttAdaptor().convertToPostAttributes(deviceSessionCtx, mqttMsg); TransportProtos.PostAttributeMsg postAttributeMsg = context.getProtoMqttAdaptor().convertToPostAttributes(deviceSessionCtx, mqttMsg);
transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getPubAckCallback(ctx, msgId, postAttributeMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), postAttributeMsg, getMetadata(deviceSessionCtx, topicName),
getPubAckCallback(ctx, msgId, postAttributeMsg));
} else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_SHORT_JSON_TOPIC)) { } else if (topicName.startsWith(MqttTopics.DEVICE_RPC_RESPONSE_SHORT_JSON_TOPIC)) {
TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = context.getJsonMqttAdaptor().convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_JSON_TOPIC); TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = context.getJsonMqttAdaptor().convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_JSON_TOPIC);
transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg)); transportService.process(deviceSessionCtx.getSessionInfo(), rpcResponseMsg, getPubAckCallback(ctx, msgId, rpcResponseMsg));
@ -525,6 +535,16 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
} }
} }
private TbMsgMetaData getMetadata(DeviceSessionCtx ctx, String topicName) {
if (ctx.isDeviceProfileMqttTransportType()) {
TbMsgMetaData md = new TbMsgMetaData();
md.putValue(DataConstants.MQTT_TOPIC, topicName);
return md;
} else {
return null;
}
}
private void sendAckOrCloseSession(ChannelHandlerContext ctx, String topicName, int msgId) { private void sendAckOrCloseSession(ChannelHandlerContext ctx, String topicName, int msgId) {
if ((deviceSessionCtx.isSendAckOnValidationException() || MqttVersion.MQTT_5.equals(deviceSessionCtx.getMqttVersion())) && msgId > 0) { if ((deviceSessionCtx.isSendAckOnValidationException() || MqttVersion.MQTT_5.equals(deviceSessionCtx.getMqttVersion())) && msgId > 0) {
log.debug("[{}] Send pub ack on invalid publish msg [{}][{}]", sessionId, topicName, msgId); log.debug("[{}] Send pub ack on invalid publish msg [{}][{}]", sessionId, topicName, msgId);

6
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/DeviceSessionCtx.java

@ -92,10 +92,14 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
private volatile boolean useJsonPayloadFormatForDefaultDownlinkTopics; private volatile boolean useJsonPayloadFormatForDefaultDownlinkTopics;
private volatile boolean sendAckOnValidationException; private volatile boolean sendAckOnValidationException;
@Getter
private volatile boolean deviceProfileMqttTransportType;
@Getter @Getter
@Setter @Setter
private TransportPayloadType provisionPayloadType = payloadType; private TransportPayloadType provisionPayloadType = payloadType;
public DeviceSessionCtx(UUID sessionId, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap, MqttTransportContext context) { public DeviceSessionCtx(UUID sessionId, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap, MqttTransportContext context) {
super(sessionId, mqttQoSMap); super(sessionId, mqttQoSMap);
this.context = context; this.context = context;
@ -165,6 +169,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
MqttDeviceProfileTransportConfiguration mqttConfig = (MqttDeviceProfileTransportConfiguration) transportConfiguration; MqttDeviceProfileTransportConfiguration mqttConfig = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttConfig.getTransportPayloadTypeConfiguration(); TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttConfig.getTransportPayloadTypeConfiguration();
payloadType = transportPayloadTypeConfiguration.getTransportPayloadType(); payloadType = transportPayloadTypeConfiguration.getTransportPayloadType();
deviceProfileMqttTransportType = true;
telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic()); telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic());
attributesPublishTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic()); attributesPublishTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic());
attributesSubscribeTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesSubscribeTopic()); attributesSubscribeTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesSubscribeTopic());
@ -179,6 +184,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter(); telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter();
attributesPublishTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter(); attributesPublishTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter();
payloadType = TransportPayloadType.JSON; payloadType = TransportPayloadType.JSON;
deviceProfileMqttTransportType = false;
sendAckOnValidationException = false; sendAckOnValidationException = false;
} }
updateAdaptor(); updateAdaptor();

55
common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/MqttTransportHandlerTest.java

@ -16,8 +16,7 @@
package org.thingsboard.server.transport.mqtt; package org.thingsboard.server.transport.mqtt;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.EmptyByteBuf; import io.netty.buffer.Unpooled;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.mqtt.MqttConnectMessage; import io.netty.handler.codec.mqtt.MqttConnectMessage;
import io.netty.handler.codec.mqtt.MqttConnectPayload; import io.netty.handler.codec.mqtt.MqttConnectPayload;
@ -34,8 +33,19 @@ import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.device.profile.DeviceProfileData;
import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration;
import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.transport.mqtt.adaptors.JsonMqttAdaptor;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -55,12 +65,14 @@ import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.is;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.BDDMockito.willDoNothing;
import static org.mockito.BDDMockito.willReturn; import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@Slf4j @Slf4j
@RunWith(MockitoJUnitRunner.class) @RunWith(MockitoJUnitRunner.class)
@ -81,9 +93,14 @@ public class MqttTransportHandlerTest {
ExecutorService executor; ExecutorService executor;
MqttTransportHandler handler; MqttTransportHandler handler;
@Spy
TransportService transportService;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
willReturn(MSG_QUEUE_LIMIT).given(context).getMessageQueueSizePerDeviceLimit(); willReturn(MSG_QUEUE_LIMIT).given(context).getMessageQueueSizePerDeviceLimit();
willReturn(transportService).given(context).getTransportService();
handler = spy(new MqttTransportHandler(context, sslHandler)); handler = spy(new MqttTransportHandler(context, sslHandler));
willReturn(IP_ADDR).given(handler).getAddress(any()); willReturn(IP_ADDR).given(handler).getAddress(any());
@ -104,9 +121,17 @@ public class MqttTransportHandlerTest {
} }
MqttPublishMessage getMqttPublishMessage() { MqttPublishMessage getMqttPublishMessage() {
return getMqttPublishMessage("v1/gateway/telemetry");
}
MqttPublishMessage getDeviceMqttPublishMessage() {
return getMqttPublishMessage("v1/devices/me/telemetry");
}
MqttPublishMessage getMqttPublishMessage(String topicName) {
MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, true, MqttQoS.AT_LEAST_ONCE, false, 123); MqttFixedHeader mqttFixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH, true, MqttQoS.AT_LEAST_ONCE, false, 123);
MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader("v1/gateway/telemetry", packedId.incrementAndGet()); MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topicName, packedId.incrementAndGet());
ByteBuf payload = new EmptyByteBuf(new PooledByteBufAllocator()); ByteBuf payload = Unpooled.wrappedBuffer("{\"testKey\":\"testValue\"}".getBytes());
return new MqttPublishMessage(mqttFixedHeader, variableHeader, payload); return new MqttPublishMessage(mqttFixedHeader, variableHeader, payload);
} }
@ -205,4 +230,26 @@ public class MqttTransportHandlerTest {
messages.forEach((msg) -> verify(handler, times(1)).processRegularSessionMsg(ctx, msg)); messages.forEach((msg) -> verify(handler, times(1)).processRegularSessionMsg(ctx, msg));
} }
@Test
public void givenMqttMessage_whenDeviceProfileMqttTransport_thenTopicAddedToMetadata() {
MqttPublishMessage message = getDeviceMqttPublishMessage();
when(context.getJsonMqttAdaptor()).thenReturn(new JsonMqttAdaptor());
handler.deviceSessionCtx.setConnected(true);
DeviceProfile deviceProfile = new DeviceProfile();
DeviceProfileData deviceProfileData = new DeviceProfileData();
MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = new MqttDeviceProfileTransportConfiguration();
mqttDeviceProfileTransportConfiguration.setTransportPayloadTypeConfiguration(new JsonTransportPayloadConfiguration());
deviceProfileData.setTransportConfiguration(mqttDeviceProfileTransportConfiguration);
deviceProfile.setProfileData(deviceProfileData);
deviceProfile.setTransportType(DeviceTransportType.MQTT);
handler.deviceSessionCtx.setDeviceProfile(deviceProfile);
handler.processRegularSessionMsg(ctx, message);
TbMsgMetaData expectedMd = new TbMsgMetaData();
expectedMd.putValue(DataConstants.MQTT_TOPIC, message.variableHeader().topicName());
verify(transportService, times(1)).process(any(), (TransportProtos.PostTelemetryMsg) any(), eq(expectedMd), any());
}
} }

5
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.common.transport;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.DeviceTransportType;
import org.thingsboard.server.common.data.rpc.RpcStatus; import org.thingsboard.server.common.data.rpc.RpcStatus;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.service.SessionMetaData; import org.thingsboard.server.common.transport.service.SessionMetaData;
@ -109,8 +110,12 @@ public interface TransportService {
void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback); void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback<Void> callback);
void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TbMsgMetaData md, TransportServiceCallback<Void> callback);
void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback<Void> callback); void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback<Void> callback);
void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TbMsgMetaData md, TransportServiceCallback<Void> callback);
void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback); void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback<Void> callback);
void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback); void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback<Void> callback);

14
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java

@ -567,6 +567,11 @@ public class DefaultTransportService implements TransportService {
@Override @Override
public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback<Void> callback) { public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback<Void> callback) {
process(sessionInfo, msg, null, callback);
}
@Override
public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TbMsgMetaData md, TransportServiceCallback<Void> callback) {
int dataPoints = 0; int dataPoints = 0;
for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) {
dataPoints += tsKv.getKvCount(); dataPoints += tsKv.getKvCount();
@ -578,7 +583,7 @@ public class DefaultTransportService implements TransportService {
CustomerId customerId = getCustomerId(sessionInfo); CustomerId customerId = getCustomerId(sessionInfo);
MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), new ApiStatsProxyCallback<>(tenantId, customerId, dataPoints, callback)); MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), new ApiStatsProxyCallback<>(tenantId, customerId, dataPoints, callback));
for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) {
TbMsgMetaData metaData = new TbMsgMetaData(); TbMsgMetaData metaData = md != null ? md.copy() : new TbMsgMetaData();
metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceName", sessionInfo.getDeviceName());
metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("deviceType", sessionInfo.getDeviceType());
metaData.putValue("ts", tsKv.getTs() + ""); metaData.putValue("ts", tsKv.getTs() + "");
@ -590,12 +595,17 @@ public class DefaultTransportService implements TransportService {
@Override @Override
public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback) { public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback<Void> callback) {
process(sessionInfo, msg, null, callback);
}
@Override
public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TbMsgMetaData md, TransportServiceCallback<Void> callback) {
if (checkLimits(sessionInfo, msg, callback, msg.getKvCount())) { if (checkLimits(sessionInfo, msg, callback, msg.getKvCount())) {
reportActivityInternal(sessionInfo); reportActivityInternal(sessionInfo);
TenantId tenantId = getTenantId(sessionInfo); TenantId tenantId = getTenantId(sessionInfo);
DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); JsonObject json = JsonUtils.getJsonObject(msg.getKvList());
TbMsgMetaData metaData = new TbMsgMetaData(); TbMsgMetaData metaData = md != null ? md.copy() : new TbMsgMetaData();
metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceName", sessionInfo.getDeviceName());
metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("deviceType", sessionInfo.getDeviceType());
if (msg.getShared()) { if (msg.getShared()) {

5
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -309,12 +309,15 @@ public class ModelConstants {
public static final String WIDGET_TYPE_TABLE_NAME = "widget_type"; public static final String WIDGET_TYPE_TABLE_NAME = "widget_type";
public static final String WIDGET_TYPE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; public static final String WIDGET_TYPE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
public static final String WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY = "bundle_alias"; public static final String WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY = "bundle_alias";
public static final String WIDGET_TYPE_ALIAS_PROPERTY = ALIAS_PROPERTY;
public static final String WIDGET_TYPE_FQN_PROPERTY = "fqn";
public static final String WIDGET_TYPE_NAME_PROPERTY = "name"; public static final String WIDGET_TYPE_NAME_PROPERTY = "name";
public static final String WIDGET_TYPE_IMAGE_PROPERTY = "image"; public static final String WIDGET_TYPE_IMAGE_PROPERTY = "image";
public static final String WIDGET_TYPE_DESCRIPTION_PROPERTY = "description"; public static final String WIDGET_TYPE_DESCRIPTION_PROPERTY = "description";
public static final String WIDGET_TYPE_DESCRIPTOR_PROPERTY = "descriptor"; public static final String WIDGET_TYPE_DESCRIPTOR_PROPERTY = "descriptor";
public static final String WIDGET_TYPE_DEPRECATED_PROPERTY = "deprecated";
/** /**
* Dashboard constants. * Dashboard constants.
*/ */

16
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java

@ -39,12 +39,15 @@ public abstract class AbstractWidgetTypeEntity<T extends BaseWidgetType> extends
@Column(name = ModelConstants.WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY) @Column(name = ModelConstants.WIDGET_TYPE_BUNDLE_ALIAS_PROPERTY)
private String bundleAlias; private String bundleAlias;
@Column(name = ModelConstants.WIDGET_TYPE_ALIAS_PROPERTY) @Column(name = ModelConstants.WIDGET_TYPE_FQN_PROPERTY)
private String alias; private String fqn;
@Column(name = ModelConstants.WIDGET_TYPE_NAME_PROPERTY) @Column(name = ModelConstants.WIDGET_TYPE_NAME_PROPERTY)
private String name; private String name;
@Column(name = ModelConstants.WIDGET_TYPE_DEPRECATED_PROPERTY)
private boolean deprecated;
public AbstractWidgetTypeEntity() { public AbstractWidgetTypeEntity() {
super(); super();
} }
@ -58,8 +61,9 @@ public abstract class AbstractWidgetTypeEntity<T extends BaseWidgetType> extends
this.tenantId = widgetType.getTenantId().getId(); this.tenantId = widgetType.getTenantId().getId();
} }
this.bundleAlias = widgetType.getBundleAlias(); this.bundleAlias = widgetType.getBundleAlias();
this.alias = widgetType.getAlias(); this.fqn = widgetType.getFqn();
this.name = widgetType.getName(); this.name = widgetType.getName();
this.deprecated = widgetType.isDeprecated();
} }
public AbstractWidgetTypeEntity(AbstractWidgetTypeEntity widgetTypeEntity) { public AbstractWidgetTypeEntity(AbstractWidgetTypeEntity widgetTypeEntity) {
@ -67,8 +71,9 @@ public abstract class AbstractWidgetTypeEntity<T extends BaseWidgetType> extends
this.setCreatedTime(widgetTypeEntity.getCreatedTime()); this.setCreatedTime(widgetTypeEntity.getCreatedTime());
this.tenantId = widgetTypeEntity.getTenantId(); this.tenantId = widgetTypeEntity.getTenantId();
this.bundleAlias = widgetTypeEntity.getBundleAlias(); this.bundleAlias = widgetTypeEntity.getBundleAlias();
this.alias = widgetTypeEntity.getAlias(); this.fqn = widgetTypeEntity.getFqn();
this.name = widgetTypeEntity.getName(); this.name = widgetTypeEntity.getName();
this.deprecated = widgetTypeEntity.isDeprecated();
} }
protected BaseWidgetType toBaseWidgetType() { protected BaseWidgetType toBaseWidgetType() {
@ -78,8 +83,9 @@ public abstract class AbstractWidgetTypeEntity<T extends BaseWidgetType> extends
widgetType.setTenantId(TenantId.fromUUID(tenantId)); widgetType.setTenantId(TenantId.fromUUID(tenantId));
} }
widgetType.setBundleAlias(bundleAlias); widgetType.setBundleAlias(bundleAlias);
widgetType.setAlias(alias); widgetType.setFqn(fqn);
widgetType.setName(name); widgetType.setName(name);
widgetType.setDeprecated(deprecated);
return widgetType; return widgetType;
} }

24
dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java

@ -64,20 +64,20 @@ public class WidgetTypeDataValidator extends DataValidator<WidgetTypeDetails> {
if (widgetsBundle == null) { if (widgetsBundle == null) {
throw new DataValidationException("Widget type is referencing to non-existent widgets bundle!"); throw new DataValidationException("Widget type is referencing to non-existent widgets bundle!");
} }
String alias = widgetTypeDetails.getAlias(); String fqn = widgetTypeDetails.getFqn();
if (alias == null || alias.trim().isEmpty()) { if (fqn == null || fqn.trim().isEmpty()) {
alias = widgetTypeDetails.getName().toLowerCase().replaceAll("\\W+", "_"); fqn = widgetTypeDetails.getName().toLowerCase().replaceAll("\\W+", "_");
} }
String originalAlias = alias; String originalFqn = fqn;
int c = 1; int c = 1;
WidgetType withSameAlias; WidgetType withSameFqn;
do { do {
withSameAlias = widgetTypeDao.findByTenantIdBundleAliasAndAlias(widgetTypeDetails.getTenantId().getId(), widgetTypeDetails.getBundleAlias(), alias); withSameFqn = widgetTypeDao.findByTenantIdAndFqn(widgetTypeDetails.getTenantId().getId(), fqn);
if (withSameAlias != null) { if (withSameFqn != null) {
alias = originalAlias + (++c); fqn = originalFqn + (++c);
} }
} while (withSameAlias != null); } while (withSameFqn != null);
widgetTypeDetails.setAlias(alias); widgetTypeDetails.setFqn(fqn);
} }
@Override @Override
@ -89,8 +89,8 @@ public class WidgetTypeDataValidator extends DataValidator<WidgetTypeDetails> {
if (!storedWidgetType.getBundleAlias().equals(widgetTypeDetails.getBundleAlias())) { if (!storedWidgetType.getBundleAlias().equals(widgetTypeDetails.getBundleAlias())) {
throw new DataValidationException("Update of widget type bundle alias is prohibited!"); throw new DataValidationException("Update of widget type bundle alias is prohibited!");
} }
if (!storedWidgetType.getAlias().equals(widgetTypeDetails.getAlias())) { if (!storedWidgetType.getFqn().equals(widgetTypeDetails.getFqn())) {
throw new DataValidationException("Update of widget type alias is prohibited!"); throw new DataValidationException("Update of widget type fqn is prohibited!");
} }
return new WidgetTypeDetails(storedWidgetType); return new WidgetTypeDetails(storedWidgetType);
} }

4
dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java

@ -73,8 +73,8 @@ public class JpaWidgetTypeDao extends JpaAbstractDao<WidgetTypeDetailsEntity, Wi
} }
@Override @Override
public WidgetType findByTenantIdBundleAliasAndAlias(UUID tenantId, String bundleAlias, String alias) { public WidgetType findByTenantIdAndFqn(UUID tenantId, String fqn) {
return DaoUtil.getData(widgetTypeRepository.findWidgetTypeByTenantIdAndBundleAliasAndAlias(tenantId, bundleAlias, alias)); return DaoUtil.getData(widgetTypeRepository.findWidgetTypeByTenantIdAndFqn(tenantId, fqn));
} }
@Override @Override

7
dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java

@ -42,10 +42,9 @@ public interface WidgetTypeRepository extends JpaRepository<WidgetTypeDetailsEnt
List<WidgetTypeDetailsEntity> findByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias); List<WidgetTypeDetailsEntity> findByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias);
@Query("SELECT wt FROM WidgetTypeEntity wt " + @Query("SELECT wt FROM WidgetTypeEntity wt " +
"WHERE wt.tenantId = :tenantId AND wt.bundleAlias = :bundleAlias AND wt.alias = :alias") "WHERE wt.tenantId = :tenantId AND wt.fqn = :fqn")
WidgetTypeEntity findWidgetTypeByTenantIdAndBundleAliasAndAlias(@Param("tenantId") UUID tenantId, WidgetTypeEntity findWidgetTypeByTenantIdAndFqn(@Param("tenantId") UUID tenantId,
@Param("bundleAlias") String bundleAlias, @Param("fqn") String fqn);
@Param("alias") String alias);
@Query(value = "SELECT * FROM widget_type wt " + @Query(value = "SELECT * FROM widget_type wt " +
"WHERE wt.tenant_id = :tenantId AND cast(wt.descriptor as json) ->> 'resources' LIKE LOWER(CONCAT('%', :resourceId, '%'))", "WHERE wt.tenant_id = :tenantId AND cast(wt.descriptor as json) ->> 'resources' LIKE LOWER(CONCAT('%', :resourceId, '%'))",

7
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java

@ -74,14 +74,13 @@ public interface WidgetTypeDao extends Dao<WidgetTypeDetails> {
List<WidgetTypeInfo> findWidgetTypesInfosByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias); List<WidgetTypeInfo> findWidgetTypesInfosByTenantIdAndBundleAlias(UUID tenantId, String bundleAlias);
/** /**
* Find widget type by tenantId, bundleAlias and alias. * Find widget type by tenantId and FQN.
* *
* @param tenantId the tenantId * @param tenantId the tenantId
* @param bundleAlias the bundle alias * @param fqn the FQN
* @param alias the alias
* @return the widget type object * @return the widget type object
*/ */
WidgetType findByTenantIdBundleAliasAndAlias(UUID tenantId, String bundleAlias, String alias); WidgetType findByTenantIdAndFqn(UUID tenantId, String fqn);
/** /**
* Find widget types infos by tenantId and resourceId in descriptor. * Find widget types infos by tenantId and resourceId in descriptor.

37
dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java

@ -28,6 +28,7 @@ import org.thingsboard.server.common.data.id.WidgetTypeId;
import org.thingsboard.server.common.data.widget.WidgetType; import org.thingsboard.server.common.data.widget.WidgetType;
import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.common.data.widget.WidgetTypeDetails;
import org.thingsboard.server.common.data.widget.WidgetTypeInfo; import org.thingsboard.server.common.data.widget.WidgetTypeInfo;
import org.thingsboard.server.dao.entity.AbstractEntityService;
import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent; import org.thingsboard.server.dao.eventsourcing.DeleteEntityEvent;
import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent; import org.thingsboard.server.dao.eventsourcing.SaveEntityEvent;
import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.DataValidator;
@ -38,7 +39,7 @@ import java.util.Optional;
@Service("WidgetTypeDaoService") @Service("WidgetTypeDaoService")
@Slf4j @Slf4j
public class WidgetTypeServiceImpl implements WidgetTypeService { public class WidgetTypeServiceImpl extends AbstractEntityService implements WidgetTypeService {
public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; public static final String INCORRECT_TENANT_ID = "Incorrect tenantId ";
public static final String INCORRECT_RESOURCE_ID = "Incorrect resourceId "; public static final String INCORRECT_RESOURCE_ID = "Incorrect resourceId ";
@ -71,10 +72,16 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
public WidgetTypeDetails saveWidgetType(WidgetTypeDetails widgetTypeDetails) { public WidgetTypeDetails saveWidgetType(WidgetTypeDetails widgetTypeDetails) {
log.trace("Executing saveWidgetType [{}]", widgetTypeDetails); log.trace("Executing saveWidgetType [{}]", widgetTypeDetails);
widgetTypeValidator.validate(widgetTypeDetails, WidgetType::getTenantId); widgetTypeValidator.validate(widgetTypeDetails, WidgetType::getTenantId);
WidgetTypeDetails result = widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails); try {
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId()) WidgetTypeDetails result = widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails);
.entityId(result.getId()).added(widgetTypeDetails.getId() == null).build()); eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId())
return result; .entityId(result.getId()).added(widgetTypeDetails.getId() == null).build());
return result;
} catch (Exception t) {
checkConstraintViolation(t,
"uq_widget_type_fqn", "Widget type with such fqn already exists!");
throw t;
}
} }
@Override @Override
@ -85,6 +92,17 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(widgetTypeId).build()); eventPublisher.publishEvent(DeleteEntityEvent.builder().tenantId(tenantId).entityId(widgetTypeId).build());
} }
@Override
public void setWidgetTypeDeprecated(TenantId tenantId, WidgetTypeId widgetTypeId, boolean deprecated) {
log.trace("Executing setWidgetTypeDeprecated, widgetTypeId [{}], deprecated [{}]", widgetTypeId, deprecated);
Validator.validateId(widgetTypeId, "Incorrect widgetTypeId " + widgetTypeId);
WidgetTypeDetails widgetTypeDetails = widgetTypeDao.findById(tenantId, widgetTypeId.getId());
if (widgetTypeDetails.isDeprecated() != deprecated) {
widgetTypeDetails.setDeprecated(deprecated);
widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails);
}
}
@Override @Override
public List<WidgetType> findWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias) { public List<WidgetType> findWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias) {
log.trace("Executing findWidgetTypesByTenantIdAndBundleAlias, tenantId [{}], bundleAlias [{}]", tenantId, bundleAlias); log.trace("Executing findWidgetTypesByTenantIdAndBundleAlias, tenantId [{}], bundleAlias [{}]", tenantId, bundleAlias);
@ -118,12 +136,11 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
} }
@Override @Override
public WidgetType findWidgetTypeByTenantIdBundleAliasAndAlias(TenantId tenantId, String bundleAlias, String alias) { public WidgetType findWidgetTypeByTenantIdAndFqn(TenantId tenantId, String fqn) {
log.trace("Executing findWidgetTypeByTenantIdBundleAliasAndAlias, tenantId [{}], bundleAlias [{}], alias [{}]", tenantId, bundleAlias, alias); log.trace("Executing findWidgetTypeByTenantIdAndFqn, tenantId [{}], fqn [{}]", tenantId, fqn);
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId); Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
Validator.validateString(bundleAlias, INCORRECT_BUNDLE_ALIAS + bundleAlias); Validator.validateString(fqn, "Incorrect fqn " + fqn);
Validator.validateString(alias, "Incorrect alias " + alias); return widgetTypeDao.findByTenantIdAndFqn(tenantId.getId(), fqn);
return widgetTypeDao.findByTenantIdBundleAliasAndAlias(tenantId.getId(), bundleAlias, alias);
} }
@Override @Override

6
dao/src/main/resources/sql/schema-entities.sql

@ -488,13 +488,15 @@ CREATE TABLE IF NOT EXISTS user_credentials (
CREATE TABLE IF NOT EXISTS widget_type ( CREATE TABLE IF NOT EXISTS widget_type (
id uuid NOT NULL CONSTRAINT widget_type_pkey PRIMARY KEY, id uuid NOT NULL CONSTRAINT widget_type_pkey PRIMARY KEY,
created_time bigint NOT NULL, created_time bigint NOT NULL,
alias varchar(255), fqn varchar(512),
bundle_alias varchar(255), bundle_alias varchar(255),
descriptor varchar(1000000), descriptor varchar(1000000),
name varchar(255), name varchar(255),
tenant_id uuid, tenant_id uuid,
image varchar(1000000), image varchar(1000000),
description varchar(255) deprecated boolean NOT NULL DEFAULT false,
description varchar(255),
CONSTRAINT uq_widget_type_fqn UNIQUE (tenant_id, fqn)
); );
CREATE TABLE IF NOT EXISTS widgets_bundle ( CREATE TABLE IF NOT EXISTS widgets_bundle (

10
dao/src/test/java/org/thingsboard/server/dao/service/WidgetTypeServiceTest.java

@ -63,7 +63,7 @@ public class WidgetTypeServiceTest extends AbstractServiceTest {
Assert.assertNotNull(savedWidgetType); Assert.assertNotNull(savedWidgetType);
Assert.assertNotNull(savedWidgetType.getId()); Assert.assertNotNull(savedWidgetType.getId());
Assert.assertNotNull(savedWidgetType.getAlias()); Assert.assertNotNull(savedWidgetType.getFqn());
Assert.assertTrue(savedWidgetType.getCreatedTime() > 0); Assert.assertTrue(savedWidgetType.getCreatedTime() > 0);
Assert.assertEquals(widgetType.getTenantId(), savedWidgetType.getTenantId()); Assert.assertEquals(widgetType.getTenantId(), savedWidgetType.getTenantId());
Assert.assertEquals(widgetType.getName(), savedWidgetType.getName()); Assert.assertEquals(widgetType.getName(), savedWidgetType.getName());
@ -211,7 +211,7 @@ public class WidgetTypeServiceTest extends AbstractServiceTest {
} }
@Test @Test
public void testUpdateWidgetTypeAlias() throws IOException { public void testUpdateWidgetTypeFqn() throws IOException {
WidgetsBundle widgetsBundle = new WidgetsBundle(); WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTenantId(tenantId); widgetsBundle.setTenantId(tenantId);
widgetsBundle.setTitle("Widgets bundle"); widgetsBundle.setTitle("Widgets bundle");
@ -223,7 +223,7 @@ public class WidgetTypeServiceTest extends AbstractServiceTest {
widgetType.setName("Widget Type"); widgetType.setName("Widget Type");
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class)); widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
WidgetTypeDetails savedWidgetType = widgetTypeService.saveWidgetType(widgetType); WidgetTypeDetails savedWidgetType = widgetTypeService.saveWidgetType(widgetType);
savedWidgetType.setAlias("some_alias"); savedWidgetType.setFqn("some_fqn");
try { try {
Assertions.assertThrows(DataValidationException.class, () -> { Assertions.assertThrows(DataValidationException.class, () -> {
widgetTypeService.saveWidgetType(savedWidgetType); widgetTypeService.saveWidgetType(savedWidgetType);
@ -254,7 +254,7 @@ public class WidgetTypeServiceTest extends AbstractServiceTest {
} }
@Test @Test
public void testFindWidgetTypeByTenantIdBundleAliasAndAlias() throws IOException { public void testFindWidgetTypeByTenantIdAndFqn() throws IOException {
WidgetsBundle widgetsBundle = new WidgetsBundle(); WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTenantId(tenantId); widgetsBundle.setTenantId(tenantId);
widgetsBundle.setTitle("Widgets bundle"); widgetsBundle.setTitle("Widgets bundle");
@ -266,7 +266,7 @@ public class WidgetTypeServiceTest extends AbstractServiceTest {
widgetType.setName("Widget Type"); widgetType.setName("Widget Type");
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class)); widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
WidgetType savedWidgetType = new WidgetType(widgetTypeService.saveWidgetType(widgetType)); WidgetType savedWidgetType = new WidgetType(widgetTypeService.saveWidgetType(widgetType));
WidgetType foundWidgetType = widgetTypeService.findWidgetTypeByTenantIdBundleAliasAndAlias(tenantId, savedWidgetsBundle.getAlias(), savedWidgetType.getAlias()); WidgetType foundWidgetType = widgetTypeService.findWidgetTypeByTenantIdAndFqn(tenantId, savedWidgetType.getFqn());
Assert.assertNotNull(foundWidgetType); Assert.assertNotNull(foundWidgetType);
Assert.assertEquals(savedWidgetType, foundWidgetType); Assert.assertEquals(savedWidgetType, foundWidgetType);

6
dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java

@ -55,7 +55,7 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest {
WidgetTypeDetails widgetType = new WidgetTypeDetails(); WidgetTypeDetails widgetType = new WidgetTypeDetails();
widgetType.setTenantId(TenantId.SYS_TENANT_ID); widgetType.setTenantId(TenantId.SYS_TENANT_ID);
widgetType.setName("WIDGET_TYPE_" + number); widgetType.setName("WIDGET_TYPE_" + number);
widgetType.setAlias("ALIAS_" + number); widgetType.setFqn("FQN_" + number);
widgetType.setBundleAlias(BUNDLE_ALIAS); widgetType.setBundleAlias(BUNDLE_ALIAS);
return widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType); return widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType);
} }
@ -74,10 +74,10 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest {
} }
@Test @Test
public void testFindByTenantIdAndBundleAliasAndAlias() { public void testFindByTenantIdAndFqn() {
WidgetType result = widgetTypeList.get(0); WidgetType result = widgetTypeList.get(0);
assertNotNull(result); assertNotNull(result);
WidgetType widgetType = widgetTypeDao.findByTenantIdBundleAliasAndAlias(TenantId.SYS_TENANT_ID.getId(), BUNDLE_ALIAS, "ALIAS_0"); WidgetType widgetType = widgetTypeDao.findByTenantIdAndFqn(TenantId.SYS_TENANT_ID.getId(), "FQN_0");
assertEquals(result.getId(), widgetType.getId()); assertEquals(result.getId(), widgetType.getId());
} }
} }

309
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java

@ -18,18 +18,16 @@ package org.thingsboard.rule.engine.math;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.awaitility.Awaitility;
import org.junit.Assert;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.mockito.verification.Timeout;
import org.springframework.test.util.ReflectionTestUtils;
import org.thingsboard.common.util.AbstractListeningExecutor; import org.thingsboard.common.util.AbstractListeningExecutor;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
@ -55,25 +53,31 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.IntStream; import java.util.stream.IntStream;
import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyDouble; import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.lenient; import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy; import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times; import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@Slf4j @Slf4j
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@ -81,10 +85,11 @@ public class TbMathNodeTest {
static final int RULE_DISPATCHER_POOL_SIZE = 2; static final int RULE_DISPATCHER_POOL_SIZE = 2;
static final int DB_CALLBACK_POOL_SIZE = 3; static final int DB_CALLBACK_POOL_SIZE = 3;
static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
private final EntityId originator = DeviceId.fromString("ccd71696-0586-422d-940e-755a41ec3b0d"); private final EntityId originator = DeviceId.fromString("ccd71696-0586-422d-940e-755a41ec3b0d");
private final TenantId tenantId = TenantId.fromUUID(UUID.fromString("e7f46b23-0c7d-42f5-9b06-fc35ab17af8a")); private final TenantId tenantId = TenantId.fromUUID(UUID.fromString("e7f46b23-0c7d-42f5-9b06-fc35ab17af8a"));
@Mock @Mock(lenient = true)
private TbContext ctx; private TbContext ctx;
@Mock @Mock
private AttributesService attributesService; private AttributesService attributesService;
@ -102,11 +107,11 @@ public class TbMathNodeTest {
ruleEngineDispatcherExecutor = new RuleDispatcherExecutor(); ruleEngineDispatcherExecutor = new RuleDispatcherExecutor();
ruleEngineDispatcherExecutor.init(); ruleEngineDispatcherExecutor.init();
lenient().when(ctx.getAttributesService()).thenReturn(attributesService); willReturn(dbCallbackExecutor).given(ctx).getDbCallbackExecutor();
lenient().when(ctx.getTelemetryService()).thenReturn(telemetryService); willReturn(attributesService).given(ctx).getAttributesService();
lenient().when(ctx.getTimeseriesService()).thenReturn(tsService); willReturn(telemetryService).given(ctx).getTelemetryService();
lenient().when(ctx.getTenantId()).thenReturn(tenantId); willReturn(tsService).given(ctx).getTimeseriesService();
lenient().when(ctx.getDbCallbackExecutor()).thenReturn(dbCallbackExecutor); willReturn(tenantId).given(ctx).getTenantId();
} }
@AfterEach @AfterEach
@ -115,10 +120,6 @@ public class TbMathNodeTest {
dbCallbackExecutor.executor().shutdownNow(); dbCallbackExecutor.executor().shutdownNow();
} }
private void initMocks() {
Mockito.clearInvocations(ctx, attributesService, tsService, telemetryService);
}
private TbMathNode initNode(TbRuleNodeMathFunctionType operation, TbMathResult result, TbMathArgument... arguments) { private TbMathNode initNode(TbRuleNodeMathFunctionType operation, TbMathResult result, TbMathArgument... arguments) {
return initNode(operation, null, result, arguments); return initNode(operation, null, result, arguments);
} }
@ -161,9 +162,6 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ConcurrentMap<EntityId, TbMathNode.SemaphoreWithQueue<TbMathNode.TbMsgTbContext>> semaphores = (ConcurrentMap<EntityId, TbMathNode.SemaphoreWithQueue<TbMathNode.TbMsgTbContext>>) ReflectionTestUtils.getField(node, "locks");
Assert.assertNotNull(semaphores);
metaData.putValue("key1", "secondMsgResult"); metaData.putValue("key1", "secondMsgResult");
metaData.putValue("key2", "argumentC"); metaData.putValue("key2", "argumentC");
msgNode = JacksonUtil.newObjectNode() msgNode = JacksonUtil.newObjectNode()
@ -172,80 +170,43 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
Awaitility.await("Semaphore released").atMost(5, TimeUnit.SECONDS).until(() -> semaphores.get(originator).semaphore.tryAcquire());
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
Mockito.verify(ctx, Mockito.times(2)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT).times(2)).tellSuccess(msgCaptor.capture());
List<TbMsg> resultMsgs = msgCaptor.getAllValues(); List<TbMsg> resultMsgs = msgCaptor.getAllValues();
Assert.assertFalse(resultMsgs.isEmpty()); assertFalse(resultMsgs.isEmpty());
Assert.assertEquals(2, resultMsgs.size()); assertEquals(2, resultMsgs.size());
for (int i = 0; i < resultMsgs.size(); i++) { for (int i = 0; i < resultMsgs.size(); i++) {
TbMsg outMsg = resultMsgs.get(i); TbMsg outMsg = resultMsgs.get(i);
Assert.assertNotNull(outMsg); assertNotNull(outMsg);
Assert.assertNotNull(outMsg.getData()); assertNotNull(outMsg.getData());
var resultJson = JacksonUtil.toJsonNode(outMsg.getData()); var resultJson = JacksonUtil.toJsonNode(outMsg.getData());
String resultKey = i == 0 ? "firstMsgResult" : "secondMsgResult"; String resultKey = i == 0 ? "firstMsgResult" : "secondMsgResult";
Assert.assertTrue(resultJson.has(resultKey)); assertTrue(resultJson.has(resultKey));
Assert.assertEquals(i == 0 ? 10 : 17, resultJson.get(resultKey).asInt()); assertEquals(i == 0 ? 10 : 17, resultJson.get(resultKey).asInt());
} }
semaphores.remove(originator);
} }
@Test private static Stream<Arguments> testSimpleTwoArgumentFunction() {
public void testSimpleFunctions() { return Stream.of(
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.ADD, 2.1, 2.2, 4.3); Arguments.of(TbRuleNodeMathFunctionType.ADD, 2.1, 2.2, 4.3),
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.SUB, 2.1, 2.2, -0.1); Arguments.of(TbRuleNodeMathFunctionType.SUB, 2.1, 2.2, -0.1),
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.MULT, 2.1, 2.0, 4.2); Arguments.of(TbRuleNodeMathFunctionType.MULT, 2.1, 2.0, 4.2),
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.DIV, 4.2, 2.0, 2.1); Arguments.of(TbRuleNodeMathFunctionType.DIV, 4.2, 2.0, 2.1),
Arguments.of(TbRuleNodeMathFunctionType.ATAN2, 0.5, 0.3, 1.03),
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SIN, Math.toRadians(30), 0.5); Arguments.of(TbRuleNodeMathFunctionType.HYPOT, 4, 5, 6.4),
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SIN, Math.toRadians(90), 1.0); Arguments.of(TbRuleNodeMathFunctionType.FLOOR_DIV, 5, 3, 1),
Arguments.of(TbRuleNodeMathFunctionType.FLOOR_MOD, 6, 3, 0),
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SINH, Math.toRadians(0), 0.0); Arguments.of(TbRuleNodeMathFunctionType.MIN, 5, 3, 3),
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.COSH, Math.toRadians(0), 1.0); Arguments.of(TbRuleNodeMathFunctionType.MAX, 5, 3, 5),
Arguments.of(TbRuleNodeMathFunctionType.POW, 5, 3, 125)
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.COS, Math.toRadians(60), 0.5); );
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.COS, Math.toRadians(0), 1.0);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.TAN, Math.toRadians(45), 1);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.TAN, Math.toRadians(0), 0);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.TANH, 90, 1);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.ACOS, 0.5, 1.05);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.ASIN, 0.5, 0.52);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.ATAN, 0.5, 0.46);
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.ATAN2, 0.5, 0.3, 1.03);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.EXP, 1, 2.72);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.EXPM1, 1, 1.72);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.ABS, -1, 1);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SQRT, 4, 2);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.CBRT, 8, 2);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.GET_EXP, 4, 2);
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.HYPOT, 4, 5, 6.4);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.LOG, 4, 1.39);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.LOG10, 4, 0.6);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.LOG1P, 4, 1.61);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.CEIL, 1.55, 2);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.FLOOR, 23.97, 23);
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.FLOOR_DIV, 5, 3, 1);
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.FLOOR_MOD, 6, 3, 0);
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.MIN, 5, 3, 3);
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.MAX, 5, 3, 5);
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.POW, 5, 3, 125);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SIGNUM, 0.55, 1);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.RAD, 5, 0.09);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.DEG, 5, 286.48);
} }
private void testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType function, double arg1, double arg2, double result) { @ParameterizedTest
initMocks(); @MethodSource
public void testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType function, double arg1, double arg2, double result) {
var node = initNode(function, var node = initNode(function,
new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null), new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null),
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a"), new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a"),
@ -257,19 +218,59 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
Mockito.verify(ctx, Mockito.timeout(5000).times(1)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT).times(1)).tellSuccess(msgCaptor.capture());
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg); assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData()); assertNotNull(resultMsg.getData());
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData()); var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultJson.has("result")); assertTrue(resultJson.has("result"));
Assert.assertEquals(result, resultJson.get("result").asDouble(), 0d); assertEquals(result, resultJson.get("result").asDouble(), 0d);
} }
private void testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType function, double arg1, double result) { private static Stream<Arguments> testSimpleOneArgumentFunction() {
initMocks(); return Stream.of(
Arguments.of(TbRuleNodeMathFunctionType.SIN, Math.toRadians(30), 0.5),
Arguments.of(TbRuleNodeMathFunctionType.SIN, Math.toRadians(90), 1.0),
Arguments.of(TbRuleNodeMathFunctionType.SINH, Math.toRadians(0), 0.0),
Arguments.of(TbRuleNodeMathFunctionType.COSH, Math.toRadians(0), 1.0),
Arguments.of(TbRuleNodeMathFunctionType.COS, Math.toRadians(60), 0.5),
Arguments.of(TbRuleNodeMathFunctionType.COS, Math.toRadians(0), 1.0),
Arguments.of(TbRuleNodeMathFunctionType.TAN, Math.toRadians(45), 1),
Arguments.of(TbRuleNodeMathFunctionType.TAN, Math.toRadians(0), 0),
Arguments.of(TbRuleNodeMathFunctionType.TANH, 90, 1),
Arguments.of(TbRuleNodeMathFunctionType.ACOS, 0.5, 1.05),
Arguments.of(TbRuleNodeMathFunctionType.ASIN, 0.5, 0.52),
Arguments.of(TbRuleNodeMathFunctionType.ATAN, 0.5, 0.46),
Arguments.of(TbRuleNodeMathFunctionType.EXP, 1, 2.72),
Arguments.of(TbRuleNodeMathFunctionType.EXPM1, 1, 1.72),
Arguments.of(TbRuleNodeMathFunctionType.ABS, -1, 1),
Arguments.of(TbRuleNodeMathFunctionType.SQRT, 4, 2),
Arguments.of(TbRuleNodeMathFunctionType.CBRT, 8, 2),
Arguments.of(TbRuleNodeMathFunctionType.GET_EXP, 4, 2),
Arguments.of(TbRuleNodeMathFunctionType.LOG, 4, 1.39),
Arguments.of(TbRuleNodeMathFunctionType.LOG10, 4, 0.6),
Arguments.of(TbRuleNodeMathFunctionType.LOG1P, 4, 1.61),
Arguments.of(TbRuleNodeMathFunctionType.CEIL, 1.55, 2),
Arguments.of(TbRuleNodeMathFunctionType.FLOOR, 23.97, 23),
Arguments.of(TbRuleNodeMathFunctionType.SIGNUM, 0.55, 1),
Arguments.of(TbRuleNodeMathFunctionType.RAD, 5, 0.09),
Arguments.of(TbRuleNodeMathFunctionType.DEG, 5, 286.48)
);
}
@ParameterizedTest
@MethodSource
public void testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType function, double arg1, double result) {
var node = initNode(function, var node = initNode(function,
new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null), new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null),
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a")
@ -280,14 +281,14 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT).times(1)).tellSuccess(msgCaptor.capture());
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg); assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData()); assertNotNull(resultMsg.getData());
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData()); var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultJson.has("result")); assertTrue(resultJson.has("result"));
Assert.assertEquals(result, resultJson.get("result").asDouble(), 0d); assertEquals(result, resultJson.get("result").asDouble(), 0d);
} }
@Test @Test
@ -303,14 +304,14 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg); assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData()); assertNotNull(resultMsg.getData());
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData()); var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultJson.has("result")); assertTrue(resultJson.has("result"));
Assert.assertEquals(4, resultJson.get("result").asInt()); assertEquals(4, resultJson.get("result").asInt());
} }
@Test @Test
@ -326,15 +327,15 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg); assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData()); assertNotNull(resultMsg.getData());
Assert.assertNotNull(resultMsg.getMetaData()); assertNotNull(resultMsg.getMetaData());
var result = resultMsg.getMetaData().getValue("result"); var result = resultMsg.getMetaData().getValue("result");
Assert.assertNotNull(result); assertNotNull(result);
Assert.assertEquals("4", result); assertEquals("4", result);
} }
@Test @Test
@ -347,23 +348,23 @@ public class TbMathNodeTest {
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().toString()); TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().toString());
Mockito.when(attributesService.find(tenantId, originator, DataConstants.SERVER_SCOPE, "a")) when(attributesService.find(tenantId, originator, DataConstants.SERVER_SCOPE, "a"))
.thenReturn(Futures.immediateFuture(Optional.of(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("a", 2.0))))); .thenReturn(Futures.immediateFuture(Optional.of(new BaseAttributeKvEntry(System.currentTimeMillis(), new DoubleDataEntry("a", 2.0)))));
Mockito.when(tsService.findLatest(tenantId, originator, "b")) when(tsService.findLatest(tenantId, originator, "b"))
.thenReturn(Futures.immediateFuture(Optional.of(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("b", 2L))))); .thenReturn(Futures.immediateFuture(Optional.of(new BasicTsKvEntry(System.currentTimeMillis(), new LongDataEntry("b", 2L)))));
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg); assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData()); assertNotNull(resultMsg.getData());
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData()); var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultJson.has("result")); assertTrue(resultJson.has("result"));
Assert.assertEquals(4, resultJson.get("result").asInt()); assertEquals(4, resultJson.get("result").asInt());
} }
@Test @Test
@ -378,14 +379,14 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg); assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData()); assertNotNull(resultMsg.getData());
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData()); var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultJson.has("result")); assertTrue(resultJson.has("result"));
Assert.assertEquals(2.236, resultJson.get("result").asDouble(), 0.0); assertEquals(2.236, resultJson.get("result").asDouble(), 0.0);
} }
@Test @Test
@ -400,14 +401,14 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg); assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData()); assertNotNull(resultMsg.getData());
var result = resultMsg.getMetaData().getValue("result"); var result = resultMsg.getMetaData().getValue("result");
Assert.assertNotNull(result); assertNotNull(result);
Assert.assertEquals("2.236", result); assertEquals("2.236", result);
} }
@Test @Test
@ -419,21 +420,21 @@ public class TbMathNodeTest {
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString());
Mockito.when(telemetryService.saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble())) when(telemetryService.saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble()))
.thenReturn(Futures.immediateFuture(null)); .thenReturn(Futures.immediateFuture(null));
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
Mockito.verify(telemetryService, times(1)).saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble()); verify(telemetryService, times(1)).saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble());
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg); assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData()); assertNotNull(resultMsg.getData());
var result = resultMsg.getMetaData().getValue("result"); var result = resultMsg.getMetaData().getValue("result");
Assert.assertNotNull(result); assertNotNull(result);
Assert.assertEquals("2.236", result); assertEquals("2.236", result);
} }
@Test @Test
@ -444,21 +445,21 @@ public class TbMathNodeTest {
); );
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString());
Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class)))
.thenReturn(Futures.immediateFuture(null)); .thenReturn(Futures.immediateFuture(null));
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
Mockito.verify(telemetryService, times(1)).saveAndNotify(any(), any(), any(TsKvEntry.class)); verify(telemetryService, times(1)).saveAndNotify(any(), any(), any(TsKvEntry.class));
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg); assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData()); assertNotNull(resultMsg.getData());
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData()); var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultJson.has("result")); assertTrue(resultJson.has("result"));
Assert.assertEquals(2.236, resultJson.get("result").asDouble(), 0.0); assertEquals(2.236, resultJson.get("result").asDouble(), 0.0);
} }
@Test @Test
@ -469,26 +470,26 @@ public class TbMathNodeTest {
); );
TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString()); TbMsg msg = TbMsg.newMsg(TbMsgType.POST_TELEMETRY_REQUEST, originator, TbMsgMetaData.EMPTY, JacksonUtil.newObjectNode().put("a", 5).toString());
Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class)))
.thenReturn(Futures.immediateFuture(null)); .thenReturn(Futures.immediateFuture(null));
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
Mockito.verify(telemetryService, times(1)).saveAndNotify(any(), any(), any(TsKvEntry.class)); verify(telemetryService, times(1)).saveAndNotify(any(), any(), any(TsKvEntry.class));
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg); assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData()); assertNotNull(resultMsg.getData());
var resultMetadata = resultMsg.getMetaData().getValue("result"); var resultMetadata = resultMsg.getMetaData().getValue("result");
var resultData = JacksonUtil.toJsonNode(resultMsg.getData()); var resultData = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultData.has("result")); assertTrue(resultData.has("result"));
Assert.assertEquals(2.236, resultData.get("result").asDouble(), 0.0); assertEquals(2.236, resultData.get("result").asDouble(), 0.0);
Assert.assertNotNull(resultMetadata); assertNotNull(resultMetadata);
Assert.assertEquals("2.236", resultMetadata); assertEquals("2.236", resultMetadata);
} }
@Test @Test
@ -503,14 +504,14 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class); ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
TbMsg resultMsg = msgCaptor.getValue(); TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg); assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData()); assertNotNull(resultMsg.getData());
var result = resultMsg.getMetaData().getValue("result"); var result = resultMsg.getMetaData().getValue("result");
Assert.assertNotNull(result); assertNotNull(result);
Assert.assertEquals("2.236", result); assertEquals("2.236", result);
} }
@Test @Test
@ -523,7 +524,7 @@ public class TbMathNodeTest {
Throwable thrown = assertThrows(RuntimeException.class, () -> { Throwable thrown = assertThrows(RuntimeException.class, () -> {
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
}); });
Assert.assertNotNull(thrown.getMessage()); assertNotNull(thrown.getMessage());
} }
@Test @Test
@ -537,7 +538,7 @@ public class TbMathNodeTest {
Throwable thrown = assertThrows(RuntimeException.class, () -> { Throwable thrown = assertThrows(RuntimeException.class, () -> {
node.onMsg(ctx, msg); node.onMsg(ctx, msg);
}); });
Assert.assertNotNull(thrown.getMessage()); assertNotNull(thrown.getMessage());
} }
@Test @Test
@ -594,16 +595,16 @@ public class TbMathNodeTest {
// submit slow msg may block all rule engine dispatcher threads // submit slow msg may block all rule engine dispatcher threads
slowMsgList.forEach(msg -> ruleEngineDispatcherExecutor.executeAsync(() -> node.onMsg(ctx, msg))); slowMsgList.forEach(msg -> ruleEngineDispatcherExecutor.executeAsync(() -> node.onMsg(ctx, msg)));
// wait until dispatcher threads started with all slowMsg // wait until dispatcher threads started with all slowMsg
verify(node, new Timeout(TimeUnit.SECONDS.toMillis(5), times(slowMsgList.size()))).onMsg(eq(ctx), argThat(slowMsgList::contains)); verify(node, timeout(TIMEOUT).times(slowMsgList.size())).onMsg(eq(ctx), argThat(slowMsgList::contains));
// submit fast have to return immediately // submit fast have to return immediately
fastMsgList.forEach(msg -> ruleEngineDispatcherExecutor.executeAsync(() -> node.onMsg(ctx, msg))); fastMsgList.forEach(msg -> ruleEngineDispatcherExecutor.executeAsync(() -> node.onMsg(ctx, msg)));
// wait until all fast messages processed // wait until all fast messages processed
verify(ctx, new Timeout(TimeUnit.SECONDS.toMillis(5), times(fastMsgList.size()))).tellSuccess(any()); verify(ctx, timeout(TIMEOUT).times(fastMsgList.size())).tellSuccess(any());
slowProcessingLatch.countDown(); slowProcessingLatch.countDown();
verify(ctx, new Timeout(TimeUnit.SECONDS.toMillis(5), times(fastMsgList.size() + slowMsgList.size()))).tellSuccess(any()); verify(ctx, timeout(TIMEOUT).times(fastMsgList.size() + slowMsgList.size())).tellSuccess(any());
verify(ctx, never()).tellFailure(any(), any()); verify(ctx, never()).tellFailure(any(), any());
} }

50
ui-ngx/src/app/core/http/widget.service.ts

@ -22,6 +22,7 @@ import { PageLink } from '@shared/models/page/page-link';
import { PageData } from '@shared/models/page/page-data'; import { PageData } from '@shared/models/page/page-data';
import { WidgetsBundle } from '@shared/models/widgets-bundle.model'; import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { import {
fullWidgetTypeFqn,
Widget, Widget,
WidgetType, WidgetType,
widgetType, widgetType,
@ -168,9 +169,7 @@ export class WidgetService {
const sizeY = Math.floor(widgetTypeInfo.sizeY); const sizeY = Math.floor(widgetTypeInfo.sizeY);
const widget: Widget = { const widget: Widget = {
typeId: type.id, typeId: type.id,
isSystemType: isSystem, typeFullFqn: widgetTypeInfo.fullFqn,
bundleAlias,
typeAlias: widgetTypeInfo.alias,
type: widgetTypeInfo.type, type: widgetTypeInfo.type,
title: widgetTypeInfo.widgetName, title: widgetTypeInfo.widgetName,
sizeX, sizeX,
@ -199,9 +198,8 @@ export class WidgetService {
); );
} }
public getWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean, public getWidgetType(fullFqn: string, config?: RequestConfig): Observable<WidgetType> {
config?: RequestConfig): Observable<WidgetType> { return this.http.get<WidgetType>(`/api/widgetType?fqn=${fullFqn}`,
return this.http.get<WidgetType>(`/api/widgetType?isSystem=${isSystem}&bundleAlias=${bundleAlias}&alias=${widgetTypeAlias}`,
defaultHttpOptionsFromConfig(config)); defaultHttpOptionsFromConfig(config));
} }
@ -227,9 +225,9 @@ export class WidgetService {
})); }));
} }
public deleteWidgetType(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean, public deleteWidgetType(fullFqn: string,
config?: RequestConfig) { config?: RequestConfig) {
return this.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, config).pipe( return this.getWidgetType(fullFqn, config).pipe(
mergeMap((widgetTypeInstance) => this.http.delete(`/api/widgetType/${widgetTypeInstance.id.id}`, mergeMap((widgetTypeInstance) => this.http.delete(`/api/widgetType/${widgetTypeInstance.id.id}`,
defaultHttpOptionsFromConfig(config)).pipe( defaultHttpOptionsFromConfig(config)).pipe(
tap(() => { tap(() => {
@ -248,48 +246,40 @@ export class WidgetService {
public getWidgetTemplate(widgetTypeParam: widgetType, public getWidgetTemplate(widgetTypeParam: widgetType,
config?: RequestConfig): Observable<WidgetInfo> { config?: RequestConfig): Observable<WidgetInfo> {
const templateWidgetType = widgetTypesData.get(widgetTypeParam); const templateWidgetType = widgetTypesData.get(widgetTypeParam);
return this.getWidgetType(templateWidgetType.template.bundleAlias, templateWidgetType.template.alias, true, return this.getWidgetType(templateWidgetType.template.fullFqn,
config).pipe( config).pipe(
map((result) => { map((result) => {
const widgetInfo = toWidgetInfo(result); const widgetInfo = toWidgetInfo(result);
widgetInfo.alias = undefined; widgetInfo.fullFqn = undefined;
return widgetInfo; return widgetInfo;
}) })
); );
} }
public createWidgetInfoCacheKey(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): string { public getWidgetInfoFromCache(fullFqn: string): WidgetInfo | undefined {
return `${isSystem ? 'sys_' : ''}${bundleAlias}_${widgetTypeAlias}`; return this.widgetsInfoInMemoryCache.get(fullFqn);
} }
public getWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): WidgetInfo | undefined { public putWidgetInfoToCache(widgetInfo: WidgetInfo) {
const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); this.widgetsInfoInMemoryCache.set(widgetInfo.fullFqn, widgetInfo);
return this.widgetsInfoInMemoryCache.get(key);
}
public putWidgetInfoToCache(widgetInfo: WidgetInfo, bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) {
const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
this.widgetsInfoInMemoryCache.set(key, widgetInfo);
} }
private widgetTypeUpdated(updatedWidgetType: WidgetType): void { private widgetTypeUpdated(updatedWidgetType: WidgetType): void {
this.deleteWidgetInfoFromCache(updatedWidgetType.bundleAlias, updatedWidgetType.alias, updatedWidgetType.tenantId.id === NULL_UUID); this.deleteWidgetInfoFromCache(fullWidgetTypeFqn(updatedWidgetType));
} }
private widgetsBundleDeleted(widgetsBundle: WidgetsBundle): void { private widgetsBundleDeleted(widgetsBundle: WidgetsBundle): void {
this.deleteWidgetsBundleFromCache(widgetsBundle.alias, widgetsBundle.tenantId.id === NULL_UUID); this.deleteWidgetsBundleFromCache(widgetsBundle.alias);
} }
public deleteWidgetInfoFromCache(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean) { public deleteWidgetInfoFromCache(fullFqn: string) {
const key = this.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); this.widgetsInfoInMemoryCache.delete(fullFqn);
this.widgetsInfoInMemoryCache.delete(key);
} }
private deleteWidgetsBundleFromCache(bundleAlias: string, isSystem: boolean) { private deleteWidgetsBundleFromCache(bundleAlias: string) {
const key = (isSystem ? 'sys_' : '') + bundleAlias; this.widgetsInfoInMemoryCache.forEach((widgetInfo, fullFqn) => {
this.widgetsInfoInMemoryCache.forEach((widgetInfo, cacheKey) => { if (widgetInfo.bundleAlias === bundleAlias) {
if (cacheKey.startsWith(key)) { this.widgetsInfoInMemoryCache.delete(fullFqn);
this.widgetsInfoInMemoryCache.delete(cacheKey);
} }
}); });
} }

35
ui-ngx/src/app/core/services/dashboard-utils.service.ts

@ -29,12 +29,20 @@ import {
GridSettings, GridSettings,
WidgetLayout WidgetLayout
} from '@shared/models/dashboard.models'; } from '@shared/models/dashboard.models';
import { deepClone, isDefined, isDefinedAndNotNull, isString, isUndefined } from '@core/utils'; import {
deepClone,
isDefined,
isDefinedAndNotNull,
isEmptyStr,
isNotEmptyStr,
isString,
isUndefined
} from '@core/utils';
import { import {
Datasource, Datasource,
datasourcesHasOnlyComparisonAggregation, datasourcesHasOnlyComparisonAggregation,
DatasourceType, DatasourceType,
defaultLegendConfig, defaultLegendConfig, isValidWidgetFullFqn,
Widget, Widget,
WidgetConfig, WidgetConfig,
WidgetConfigMode, WidgetConfigMode,
@ -214,12 +222,11 @@ export class DashboardUtilsService {
public validateAndUpdateWidget(widget: Widget): Widget { public validateAndUpdateWidget(widget: Widget): Widget {
widget.config = this.validateAndUpdateWidgetConfig(widget.config, widget.type); widget.config = this.validateAndUpdateWidgetConfig(widget.config, widget.type);
widget = this.validateAndUpdateWidgetTypeFqn(widget);
// Temp workaround // Temp workaround
if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') { if (['system.charts.state_chart',
widget.typeAlias = 'basic_timeseries'; 'system.charts.basic_timeseries',
} 'system.charts.timeseries_bars_flot'].includes(widget.typeFullFqn)) {
if (widget.isSystemType && widget.bundleAlias === 'charts' &&
['state_chart', 'basic_timeseries', 'timeseries_bars_flot'].includes(widget.typeAlias)) {
const widgetConfig = widget.config; const widgetConfig = widget.config;
const widgetSettings = widget.config.settings; const widgetSettings = widget.config.settings;
if (isDefinedAndNotNull(widgetConfig.showLegend)) { if (isDefinedAndNotNull(widgetConfig.showLegend)) {
@ -238,6 +245,20 @@ export class DashboardUtilsService {
return widget; return widget;
} }
private validateAndUpdateWidgetTypeFqn(widget: Widget): Widget {
if (!isValidWidgetFullFqn(widget.typeFullFqn)) {
const w = widget as any;
if (isDefinedAndNotNull(w.isSystemType) && isNotEmptyStr(w.bundleAlias) && isNotEmptyStr(w.typeAlias)) {
widget.typeFullFqn = (w.isSystemType ? 'system' : 'tenant') + '.' + w.bundleAlias + '.' + w.typeAlias;
}
}
// Temp workaround
if (widget.typeFullFqn === 'system.charts.timeseries') {
widget.typeFullFqn = 'system.charts.basic_timeseries';
}
return widget;
}
public validateAndUpdateWidgetConfig(widgetConfig: WidgetConfig | undefined, type: widgetType): WidgetConfig { public validateAndUpdateWidgetConfig(widgetConfig: WidgetConfig | undefined, type: widgetType): WidgetConfig {
if (!widgetConfig) { if (!widgetConfig) {
widgetConfig = {}; widgetConfig = {};

4
ui-ngx/src/app/modules/dashboard/dashboard-pages.routing.module.ts

@ -38,9 +38,7 @@ export class WidgetEditorDashboardResolver implements Resolve<Dashboard> {
resolve(route: ActivatedRouteSnapshot): Dashboard { resolve(route: ActivatedRouteSnapshot): Dashboard {
const editWidgetInfo = this.utils.editWidgetInfo; const editWidgetInfo = this.utils.editWidgetInfo;
const widget: Widget = { const widget: Widget = {
isSystemType: true, typeFullFqn: 'system.customWidget',
bundleAlias: 'customWidgetBundle',
typeAlias: 'customWidget',
type: editWidgetInfo.type, type: editWidgetInfo.type,
title: 'My widget', title: 'My widget',
image: null, image: null,

8
ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts

@ -515,9 +515,7 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
// @ts-ignore // @ts-ignore
const stateController: IStateController = { const stateController: IStateController = {
getStateParams(): StateParams { getStateParams: (): StateParams => ({})
return {};
}
}; };
const filters: Filters = {}; const filters: Filters = {};
@ -587,9 +585,7 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
const sizeY = widgetInfo.sizeY * 2; const sizeY = widgetInfo.sizeY * 2;
const col = Math.floor(Math.max(0, (20 - sizeX) / 2)); const col = Math.floor(Math.max(0, (20 - sizeX) / 2));
const widget: Widget = { const widget: Widget = {
isSystemType: isSystem, typeFullFqn: widgetInfo.fullFqn,
bundleAlias,
typeAlias: widgetInfo.alias,
type: widgetInfo.type, type: widgetInfo.type,
title: widgetInfo.widgetName, title: widgetInfo.widgetName,
sizeX, sizeX,

6
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts

@ -1151,16 +1151,14 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC
addWidgetFromType(widget: WidgetInfo) { addWidgetFromType(widget: WidgetInfo) {
this.onAddWidgetClosed(); this.onAddWidgetClosed();
this.searchBundle = ''; this.searchBundle = '';
this.widgetComponentService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).subscribe( this.widgetComponentService.getWidgetInfo(widget.typeFullFqn).subscribe(
(widgetTypeInfo) => { (widgetTypeInfo) => {
const config: WidgetConfig = this.dashboardUtils.widgetConfigFromWidgetType(widgetTypeInfo); const config: WidgetConfig = this.dashboardUtils.widgetConfigFromWidgetType(widgetTypeInfo);
if (!config.title) { if (!config.title) {
config.title = 'New ' + widgetTypeInfo.widgetName; config.title = 'New ' + widgetTypeInfo.widgetName;
} }
let newWidget: Widget = { let newWidget: Widget = {
isSystemType: widget.isSystemType, typeFullFqn: widgetTypeInfo.fullFqn,
bundleAlias: widget.bundleAlias,
typeAlias: widgetTypeInfo.alias,
type: widgetTypeInfo.type, type: widgetTypeInfo.type,
title: 'New widget', title: 'New widget',
image: null, image: null,

6
ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts

@ -19,7 +19,7 @@ import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
import { NULL_UUID } from '@shared/models/id/has-uuid'; import { NULL_UUID } from '@shared/models/id/has-uuid';
import { WidgetService } from '@core/http/widget.service'; import { WidgetService } from '@core/http/widget.service';
import { WidgetInfo, widgetType } from '@shared/models/widget.models'; import { fullWidgetTypeFqn, WidgetInfo, widgetType } from '@shared/models/widget.models';
import { distinctUntilChanged, map, publishReplay, refCount, share, switchMap, tap } from 'rxjs/operators'; import { distinctUntilChanged, map, publishReplay, refCount, share, switchMap, tap } from 'rxjs/operators';
import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs'; import { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
@ -116,9 +116,7 @@ export class DashboardWidgetSelectComponent implements OnInit {
const widgetInfos = widgets.map((widgetTypeInfo) => { const widgetInfos = widgets.map((widgetTypeInfo) => {
widgetTypes.add(widgetTypeInfo.widgetType); widgetTypes.add(widgetTypeInfo.widgetType);
const widget: WidgetInfo = { const widget: WidgetInfo = {
isSystemType: isSystem, typeFullFqn: fullWidgetTypeFqn(widgetTypeInfo),
bundleAlias,
typeAlias: widgetTypeInfo.alias,
type: widgetTypeInfo.widgetType, type: widgetTypeInfo.widgetType,
title: widgetTypeInfo.name, title: widgetTypeInfo.name,
image: widgetTypeInfo.image, image: widgetTypeInfo.image,

7
ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts

@ -744,9 +744,9 @@ export class ImportExportService {
return false; return false;
} }
const widget = widgetItem.widget; const widget = widgetItem.widget;
if (isUndefined(widget.isSystemType) || if ((isUndefined(widget.typeFullFqn) && (isUndefined((widget as any).isSystemType) ||
isUndefined(widget.bundleAlias) || isUndefined((widget as any).bundleAlias) ||
isUndefined(widget.typeAlias) || isUndefined((widget as any).typeAlias))) ||
isUndefined(widget.type)) { isUndefined(widget.type)) {
return false; return false;
} }
@ -982,6 +982,7 @@ export class ImportExportService {
} }
private prepareDashboardExport(dashboard: Dashboard): Dashboard { private prepareDashboardExport(dashboard: Dashboard): Dashboard {
dashboard = this.dashboardUtils.validateAndUpdateDashboard(dashboard);
dashboard = this.prepareExport(dashboard); dashboard = this.prepareExport(dashboard);
delete dashboard.assignedCustomers; delete dashboard.assignedCustomers;
return dashboard; return dashboard;

60
ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts

@ -85,7 +85,9 @@ export class WidgetComponentService {
this.editingWidgetType = toWidgetType( this.editingWidgetType = toWidgetType(
{ {
widgetName: this.utils.editWidgetInfo.widgetName, widgetName: this.utils.editWidgetInfo.widgetName,
alias: 'customWidget', bundleAlias: 'customWidgetBundle',
fullFqn: 'system.customWidget',
deprecated: false,
type: this.utils.editWidgetInfo.type, type: this.utils.editWidgetInfo.type,
sizeX: this.utils.editWidgetInfo.sizeX, sizeX: this.utils.editWidgetInfo.sizeX,
sizeY: this.utils.editWidgetInfo.sizeY, sizeY: this.utils.editWidgetInfo.sizeY,
@ -212,7 +214,7 @@ export class WidgetComponentService {
} }
public getInstantWidgetInfo(widget: Widget): WidgetInfo { public getInstantWidgetInfo(widget: Widget): WidgetInfo {
const widgetInfo = this.widgetService.getWidgetInfoFromCache(widget.bundleAlias, widget.typeAlias, widget.isSystemType); const widgetInfo = this.widgetService.getWidgetInfoFromCache(widget.typeFullFqn);
if (widgetInfo) { if (widgetInfo) {
return widgetInfo; return widgetInfo;
} else { } else {
@ -220,42 +222,41 @@ export class WidgetComponentService {
} }
} }
public getWidgetInfo(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable<WidgetInfo> { public getWidgetInfo(fullFqn: string): Observable<WidgetInfo> {
return this.init().pipe( return this.init().pipe(
mergeMap(() => this.getWidgetInfoInternal(bundleAlias, widgetTypeAlias, isSystem)) mergeMap(() => this.getWidgetInfoInternal(fullFqn))
); );
} }
public clearWidgetInfo(widgetInfo: WidgetInfo, bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): void { public clearWidgetInfo(widgetInfo: WidgetInfo): void {
this.dynamicComponentFactoryService.destroyDynamicComponent(widgetInfo.componentType); this.dynamicComponentFactoryService.destroyDynamicComponent(widgetInfo.componentType);
this.widgetService.deleteWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem); this.widgetService.deleteWidgetInfoFromCache(widgetInfo.fullFqn);
} }
private getWidgetInfoInternal(bundleAlias: string, widgetTypeAlias: string, isSystem: boolean): Observable<WidgetInfo> { private getWidgetInfoInternal(fullFqn: string): Observable<WidgetInfo> {
const widgetInfoSubject = new ReplaySubject<WidgetInfo>(); const widgetInfoSubject = new ReplaySubject<WidgetInfo>();
const widgetInfo = this.widgetService.getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem); const widgetInfo = this.widgetService.getWidgetInfoFromCache(fullFqn);
if (widgetInfo) { if (widgetInfo) {
widgetInfoSubject.next(widgetInfo); widgetInfoSubject.next(widgetInfo);
widgetInfoSubject.complete(); widgetInfoSubject.complete();
} else { } else {
if (this.utils.widgetEditMode) { if (this.utils.widgetEditMode) {
this.loadWidget(this.editingWidgetType, bundleAlias, isSystem, widgetInfoSubject); this.loadWidget(this.editingWidgetType, widgetInfoSubject);
} else { } else {
const key = this.widgetService.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem); let fetchQueue = this.widgetsInfoFetchQueue.get(fullFqn);
let fetchQueue = this.widgetsInfoFetchQueue.get(key);
if (fetchQueue) { if (fetchQueue) {
fetchQueue.push(widgetInfoSubject); fetchQueue.push(widgetInfoSubject);
} else { } else {
fetchQueue = new Array<Subject<WidgetInfo>>(); fetchQueue = new Array<Subject<WidgetInfo>>();
this.widgetsInfoFetchQueue.set(key, fetchQueue); this.widgetsInfoFetchQueue.set(fullFqn, fetchQueue);
this.widgetService.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, {ignoreErrors: true}).subscribe( this.widgetService.getWidgetType(fullFqn, {ignoreErrors: true}).subscribe(
(widgetType) => { (widgetType) => {
this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject); this.loadWidget(widgetType, widgetInfoSubject);
}, },
() => { () => {
widgetInfoSubject.next(this.missingWidgetType); widgetInfoSubject.next(this.missingWidgetType);
widgetInfoSubject.complete(); widgetInfoSubject.complete();
this.resolveWidgetsInfoFetchQueue(key, this.missingWidgetType); this.resolveWidgetsInfoFetchQueue(fullFqn, this.missingWidgetType);
} }
); );
} }
@ -264,19 +265,18 @@ export class WidgetComponentService {
return widgetInfoSubject.asObservable(); return widgetInfoSubject.asObservable();
} }
private loadWidget(widgetType: WidgetType, bundleAlias: string, isSystem: boolean, widgetInfoSubject: Subject<WidgetInfo>) { private loadWidget(widgetType: WidgetType, widgetInfoSubject: Subject<WidgetInfo>) {
const widgetInfo = toWidgetInfo(widgetType); const widgetInfo = toWidgetInfo(widgetType);
const key = this.widgetService.createWidgetInfoCacheKey(bundleAlias, widgetInfo.alias, isSystem);
let widgetControllerDescriptor: WidgetControllerDescriptor = null; let widgetControllerDescriptor: WidgetControllerDescriptor = null;
try { try {
widgetControllerDescriptor = this.createWidgetControllerDescriptor(widgetInfo, key); widgetControllerDescriptor = this.createWidgetControllerDescriptor(widgetInfo);
} catch (e) { } catch (e) {
const details = this.utils.parseException(e); const details = this.utils.parseException(e);
const errorMessage = `Failed to compile widget script. \n Error: ${details.message}`; const errorMessage = `Failed to compile widget script. \n Error: ${details.message}`;
this.processWidgetLoadError([errorMessage], key, widgetInfoSubject); this.processWidgetLoadError([errorMessage], widgetInfo.fullFqn, widgetInfoSubject);
} }
if (widgetControllerDescriptor) { if (widgetControllerDescriptor) {
const widgetNamespace = `widget-type-${(isSystem ? 'sys-' : '')}${bundleAlias}-${widgetInfo.alias}`; const widgetNamespace = `widget-type-${widgetInfo.fullFqn.replace(/\./g, '-')}`;
this.loadWidgetResources(widgetInfo, widgetNamespace, [SharedModule, WidgetComponentsModule, this.homeComponentsModule]).subscribe( this.loadWidgetResources(widgetInfo, widgetNamespace, [SharedModule, WidgetComponentsModule, this.homeComponentsModule]).subscribe(
() => { () => {
if (widgetControllerDescriptor.settingsSchema) { if (widgetControllerDescriptor.settingsSchema) {
@ -291,15 +291,15 @@ export class WidgetComponentService {
widgetInfo.typeParameters = widgetControllerDescriptor.typeParameters; widgetInfo.typeParameters = widgetControllerDescriptor.typeParameters;
widgetInfo.actionSources = widgetControllerDescriptor.actionSources; widgetInfo.actionSources = widgetControllerDescriptor.actionSources;
widgetInfo.widgetTypeFunction = widgetControllerDescriptor.widgetTypeFunction; widgetInfo.widgetTypeFunction = widgetControllerDescriptor.widgetTypeFunction;
this.widgetService.putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem); this.widgetService.putWidgetInfoToCache(widgetInfo);
if (widgetInfoSubject) { if (widgetInfoSubject) {
widgetInfoSubject.next(widgetInfo); widgetInfoSubject.next(widgetInfo);
widgetInfoSubject.complete(); widgetInfoSubject.complete();
} }
this.resolveWidgetsInfoFetchQueue(key, widgetInfo); this.resolveWidgetsInfoFetchQueue(widgetInfo.fullFqn, widgetInfo);
}, },
(errorMessages: string[]) => { (errorMessages: string[]) => {
this.processWidgetLoadError(errorMessages, key, widgetInfoSubject); this.processWidgetLoadError(errorMessages, widgetInfo.fullFqn, widgetInfoSubject);
} }
); );
} }
@ -418,8 +418,8 @@ export class WidgetComponentService {
} }
} }
private createWidgetControllerDescriptor(widgetInfo: WidgetInfo, name: string): WidgetControllerDescriptor { private createWidgetControllerDescriptor(widgetInfo: WidgetInfo): WidgetControllerDescriptor {
let widgetTypeFunctionBody = `return function _${name} (ctx) {\n` + let widgetTypeFunctionBody = `return function _${widgetInfo.fullFqn.replace(/\./g, '_')} (ctx) {\n` +
' var self = this;\n' + ' var self = this;\n' +
' self.ctx = ctx;\n\n'; /*+ ' self.ctx = ctx;\n\n'; /*+
@ -566,18 +566,18 @@ export class WidgetComponentService {
} }
} }
private processWidgetLoadError(errorMessages: string[], cacheKey: string, widgetInfoSubject: Subject<WidgetInfo>) { private processWidgetLoadError(errorMessages: string[], fullFqn: string, widgetInfoSubject: Subject<WidgetInfo>) {
if (widgetInfoSubject) { if (widgetInfoSubject) {
widgetInfoSubject.error({ widgetInfoSubject.error({
widgetInfo: this.errorWidgetType, widgetInfo: this.errorWidgetType,
errorMessages errorMessages
}); });
} }
this.resolveWidgetsInfoFetchQueue(cacheKey, this.errorWidgetType, errorMessages); this.resolveWidgetsInfoFetchQueue(fullFqn, this.errorWidgetType, errorMessages);
} }
private resolveWidgetsInfoFetchQueue(key: string, widgetInfo: WidgetInfo, errorMessages?: string[]) { private resolveWidgetsInfoFetchQueue(fullFqn: string, widgetInfo: WidgetInfo, errorMessages?: string[]) {
const fetchQueue = this.widgetsInfoFetchQueue.get(key); const fetchQueue = this.widgetsInfoFetchQueue.get(fullFqn);
if (fetchQueue) { if (fetchQueue) {
fetchQueue.forEach(subject => { fetchQueue.forEach(subject => {
if (!errorMessages) { if (!errorMessages) {
@ -590,7 +590,7 @@ export class WidgetComponentService {
}); });
} }
}); });
this.widgetsInfoFetchQueue.delete(key); this.widgetsInfoFetchQueue.delete(fullFqn);
} }
} }
} }

17
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -21,10 +21,8 @@ import {
ComponentRef, ComponentRef,
forwardRef, forwardRef,
Input, Input,
OnChanges,
OnDestroy, OnDestroy,
OnInit, OnInit,
SimpleChanges,
ViewChild, ViewChild,
ViewContainerRef ViewContainerRef
} from '@angular/core'; } from '@angular/core';
@ -110,7 +108,7 @@ const defaultSettingsForm = [
} }
] ]
}) })
export class WidgetConfigComponent extends PageComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator, OnChanges { export class WidgetConfigComponent extends PageComponent implements OnInit, OnDestroy, ControlValueAccessor, Validator {
@ViewChild('basicModeContainer', {read: ViewContainerRef, static: false}) basicModeContainer: ViewContainerRef; @ViewChild('basicModeContainer', {read: ViewContainerRef, static: false}) basicModeContainer: ViewContainerRef;
@ -253,19 +251,6 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, OnDe
}); });
} }
ngOnChanges(changes: SimpleChanges): void {
for (const propName of Object.keys(changes)) {
const change = changes[propName];
if (!change.firstChange && change.currentValue !== change.previousValue) {
if (propName === 'widgetConfigMode') {
if (this.hasBasicModeDirective) {
this.setupConfig();
}
}
}
}
}
ngOnDestroy(): void { ngOnDestroy(): void {
this.destroyBasicModeComponent(); this.destroyBasicModeComponent();
this.removeChangeSubscriptions(); this.removeChangeSubscriptions();

7
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -301,7 +301,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.subscriptionContext.widgetUtils = this.widgetContext.utils; this.subscriptionContext.widgetUtils = this.widgetContext.utils;
this.subscriptionContext.getServerTimeDiff = this.dashboardService.getServerTimeDiff.bind(this.dashboardService); this.subscriptionContext.getServerTimeDiff = this.dashboardService.getServerTimeDiff.bind(this.dashboardService);
this.widgetComponentService.getWidgetInfo(this.widget.bundleAlias, this.widget.typeAlias, this.widget.isSystemType).subscribe( this.widgetComponentService.getWidgetInfo(this.widget.typeFullFqn).subscribe(
(widgetInfo) => { (widgetInfo) => {
this.widgetInfo = widgetInfo; this.widgetInfo = widgetInfo;
this.loadFromWidgetInfo(); this.loadFromWidgetInfo();
@ -404,7 +404,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
private loadFromWidgetInfo() { private loadFromWidgetInfo() {
this.widgetContext.widgetNamespace = this.widgetContext.widgetNamespace =
`widget-type-${(this.widget.isSystemType ? 'sys-' : '')}${this.widget.bundleAlias}-${this.widget.typeAlias}`; `widget-type-${this.widget.typeFullFqn.replace(/\./g, '-')}`;
const elem = this.elementRef.nativeElement; const elem = this.elementRef.nativeElement;
elem.classList.add('tb-widget'); elem.classList.add('tb-widget');
elem.classList.add(this.widgetContext.widgetNamespace); elem.classList.add(this.widgetContext.widgetNamespace);
@ -761,8 +761,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
} }
this.widgetContentContainer.clear(); this.widgetContentContainer.clear();
this.handleWidgetException(e); this.handleWidgetException(e);
this.widgetComponentService.clearWidgetInfo(this.widgetInfo, this.widget.bundleAlias, this.widget.typeAlias, this.widgetComponentService.clearWidgetInfo(this.widgetInfo);
this.widget.isSystemType);
throw e; throw e;
} }

107
ui-ngx/src/app/modules/home/models/widget-component.models.ts

@ -20,6 +20,7 @@ import {
Datasource, Datasource,
DatasourceData, DatasourceData,
FormattedData, FormattedData,
fullWidgetTypeFqn,
JsonSettingsSchema, JsonSettingsSchema,
Widget, Widget,
WidgetActionDescriptor, WidgetActionDescriptor,
@ -30,6 +31,7 @@ import {
widgetType, widgetType,
WidgetTypeDescriptor, WidgetTypeDescriptor,
WidgetTypeDetails, WidgetTypeDetails,
widgetTypeFqn,
WidgetTypeParameters WidgetTypeParameters
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models';
@ -84,6 +86,7 @@ import { DomSanitizer } from '@angular/platform-browser';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { EdgeService } from '@core/http/edge.service'; import { EdgeService } from '@core/http/edge.service';
import * as RxJS from 'rxjs'; import * as RxJS from 'rxjs';
import { BehaviorSubject, Observable } from 'rxjs';
import * as RxJSOperators from 'rxjs/operators'; import * as RxJSOperators from 'rxjs/operators';
import { TbPopoverComponent } from '@shared/components/popover.component'; import { TbPopoverComponent } from '@shared/components/popover.component';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
@ -92,7 +95,6 @@ import { MillisecondsToTimeStringPipe, TelemetrySubscriber } from '@app/shared/p
import { UserId } from '@shared/models/id/user-id'; import { UserId } from '@shared/models/id/user-id';
import { UserSettingsService } from '@core/http/user-settings.service'; import { UserSettingsService } from '@core/http/user-settings.service';
import { DynamicComponentModule } from '@core/services/dynamic-component-factory.service'; import { DynamicComponentModule } from '@core/services/dynamic-component-factory.service';
import { BehaviorSubject, Observable } from 'rxjs';
export interface IWidgetAction { export interface IWidgetAction {
name: string; name: string;
@ -446,11 +448,13 @@ export class WidgetContext {
return new PageLink(pageSize, page, textSearch, sortOrder); return new PageLink(pageSize, page, textSearch, sortOrder);
} }
timePageLink(startTime: number, endTime: number, pageSize: number, page: number = 0, textSearch: string = null, sortOrder: SortOrder = null) { timePageLink(startTime: number, endTime: number, pageSize: number, page: number = 0,
textSearch: string = null, sortOrder: SortOrder = null) {
return new TimePageLink(pageSize, page, textSearch, sortOrder, startTime, endTime); return new TimePageLink(pageSize, page, textSearch, sortOrder, startTime, endTime);
} }
alarmQuery(entityId: EntityId, pageLink: TimePageLink, searchStatus: AlarmSearchStatus, status: AlarmStatus, fetchOriginator: boolean, assigneeId: UserId) { alarmQuery(entityId: EntityId, pageLink: TimePageLink, searchStatus: AlarmSearchStatus,
status: AlarmStatus, fetchOriginator: boolean, assigneeId: UserId) {
return new AlarmQuery(entityId, pageLink, searchStatus, status, fetchOriginator, assigneeId); return new AlarmQuery(entityId, pageLink, searchStatus, status, fetchOriginator, assigneeId);
} }
} }
@ -505,7 +509,9 @@ export interface IDynamicWidgetComponent {
export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescriptor { export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescriptor {
widgetName: string; widgetName: string;
alias: string; bundleAlias: string;
fullFqn: string;
deprecated: boolean;
typeSettingsSchema?: string | any; typeSettingsSchema?: string | any;
typeDataKeySettingsSchema?: string | any; typeDataKeySettingsSchema?: string | any;
typeLatestDataKeySettingsSchema?: string | any; typeLatestDataKeySettingsSchema?: string | any;
@ -536,7 +542,9 @@ export interface WidgetConfigComponentData {
export const MissingWidgetType: WidgetInfo = { export const MissingWidgetType: WidgetInfo = {
type: widgetType.latest, type: widgetType.latest,
widgetName: 'Widget type not found', widgetName: 'Widget type not found',
alias: 'undefined', bundleAlias: 'undefined',
fullFqn: 'undefined',
deprecated: false,
sizeX: 8, sizeX: 8,
sizeY: 6, sizeY: 6,
resources: [], resources: [],
@ -560,7 +568,9 @@ export const MissingWidgetType: WidgetInfo = {
export const ErrorWidgetType: WidgetInfo = { export const ErrorWidgetType: WidgetInfo = {
type: widgetType.latest, type: widgetType.latest,
widgetName: 'Error loading widget', widgetName: 'Error loading widget',
alias: 'error', bundleAlias: 'error',
fullFqn: 'error',
deprecated: false,
sizeX: 8, sizeX: 8,
sizeY: 6, sizeY: 6,
resources: [], resources: [],
@ -599,48 +609,38 @@ export interface WidgetTypeInstance {
onDestroy?: () => void; onDestroy?: () => void;
} }
export function detailsToWidgetInfo(widgetTypeDetailsEntity: WidgetTypeDetails): WidgetInfo { export const toWidgetInfo = (widgetTypeEntity: WidgetType): WidgetInfo => ({
widgetName: widgetTypeEntity.name,
bundleAlias: widgetTypeEntity.bundleAlias,
fullFqn: fullWidgetTypeFqn(widgetTypeEntity),
deprecated: widgetTypeEntity.deprecated,
type: widgetTypeEntity.descriptor.type,
sizeX: widgetTypeEntity.descriptor.sizeX,
sizeY: widgetTypeEntity.descriptor.sizeY,
resources: widgetTypeEntity.descriptor.resources,
templateHtml: widgetTypeEntity.descriptor.templateHtml,
templateCss: widgetTypeEntity.descriptor.templateCss,
controllerScript: widgetTypeEntity.descriptor.controllerScript,
settingsSchema: widgetTypeEntity.descriptor.settingsSchema,
dataKeySettingsSchema: widgetTypeEntity.descriptor.dataKeySettingsSchema,
latestDataKeySettingsSchema: widgetTypeEntity.descriptor.latestDataKeySettingsSchema,
settingsDirective: widgetTypeEntity.descriptor.settingsDirective,
dataKeySettingsDirective: widgetTypeEntity.descriptor.dataKeySettingsDirective,
latestDataKeySettingsDirective: widgetTypeEntity.descriptor.latestDataKeySettingsDirective,
hasBasicMode: widgetTypeEntity.descriptor.hasBasicMode,
basicModeDirective: widgetTypeEntity.descriptor.basicModeDirective,
defaultConfig: widgetTypeEntity.descriptor.defaultConfig
});
export const detailsToWidgetInfo = (widgetTypeDetailsEntity: WidgetTypeDetails): WidgetInfo => {
const widgetInfo = toWidgetInfo(widgetTypeDetailsEntity); const widgetInfo = toWidgetInfo(widgetTypeDetailsEntity);
widgetInfo.image = widgetTypeDetailsEntity.image; widgetInfo.image = widgetTypeDetailsEntity.image;
widgetInfo.description = widgetTypeDetailsEntity.description; widgetInfo.description = widgetTypeDetailsEntity.description;
return widgetInfo; return widgetInfo;
} };
export function toWidgetInfo(widgetTypeEntity: WidgetType): WidgetInfo {
return {
widgetName: widgetTypeEntity.name,
alias: widgetTypeEntity.alias,
type: widgetTypeEntity.descriptor.type,
sizeX: widgetTypeEntity.descriptor.sizeX,
sizeY: widgetTypeEntity.descriptor.sizeY,
resources: widgetTypeEntity.descriptor.resources,
templateHtml: widgetTypeEntity.descriptor.templateHtml,
templateCss: widgetTypeEntity.descriptor.templateCss,
controllerScript: widgetTypeEntity.descriptor.controllerScript,
settingsSchema: widgetTypeEntity.descriptor.settingsSchema,
dataKeySettingsSchema: widgetTypeEntity.descriptor.dataKeySettingsSchema,
latestDataKeySettingsSchema: widgetTypeEntity.descriptor.latestDataKeySettingsSchema,
settingsDirective: widgetTypeEntity.descriptor.settingsDirective,
dataKeySettingsDirective: widgetTypeEntity.descriptor.dataKeySettingsDirective,
latestDataKeySettingsDirective: widgetTypeEntity.descriptor.latestDataKeySettingsDirective,
hasBasicMode: widgetTypeEntity.descriptor.hasBasicMode,
basicModeDirective: widgetTypeEntity.descriptor.basicModeDirective,
defaultConfig: widgetTypeEntity.descriptor.defaultConfig
};
}
export function toWidgetTypeDetails(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId,
bundleAlias: string, createdTime: number): WidgetTypeDetails {
const widgetTypeEntity = toWidgetType(widgetInfo, id, tenantId, bundleAlias, createdTime);
const widgetTypeDetails: WidgetTypeDetails = {...widgetTypeEntity,
description: widgetInfo.description,
image: widgetInfo.image
};
return widgetTypeDetails;
}
export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId, export const toWidgetType = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId,
bundleAlias: string, createdTime: number): WidgetType { bundleAlias: string, createdTime: number): WidgetType => {
const descriptor: WidgetTypeDescriptor = { const descriptor: WidgetTypeDescriptor = {
type: widgetInfo.type, type: widgetInfo.type,
sizeX: widgetInfo.sizeX, sizeX: widgetInfo.sizeX,
@ -664,14 +664,25 @@ export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId:
tenantId, tenantId,
createdTime, createdTime,
bundleAlias, bundleAlias,
alias: widgetInfo.alias, fqn: widgetTypeFqn(widgetInfo.fullFqn),
name: widgetInfo.widgetName, name: widgetInfo.widgetName,
deprecated: widgetInfo.deprecated,
descriptor descriptor
}; };
} };
export const toWidgetTypeDetails = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId,
bundleAlias: string, createdTime: number): WidgetTypeDetails => {
const widgetTypeEntity = toWidgetType(widgetInfo, id, tenantId, bundleAlias, createdTime);
return {
...widgetTypeEntity,
description: widgetInfo.description,
image: widgetInfo.image
};
};
export function updateEntityParams(params: StateParams, targetEntityParamName?: string, targetEntityId?: EntityId, export const updateEntityParams = (params: StateParams, targetEntityParamName?: string, targetEntityId?: EntityId,
entityName?: string, entityLabel?: string) { entityName?: string, entityLabel?: string) => {
if (targetEntityId) { if (targetEntityId) {
let targetEntityParams: StateParams; let targetEntityParams: StateParams;
if (targetEntityParamName && targetEntityParamName.length) { if (targetEntityParamName && targetEntityParamName.length) {
@ -692,4 +703,4 @@ export function updateEntityParams(params: StateParams, targetEntityParamName?:
targetEntityParams.entityLabel = entityLabel; targetEntityParams.entityLabel = entityLabel;
} }
} }
} };

2
ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts

@ -564,7 +564,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe
(saveWidgetAsData) => { (saveWidgetAsData) => {
if (saveWidgetAsData) { if (saveWidgetAsData) {
this.widget.widgetName = saveWidgetAsData.widgetName; this.widget.widgetName = saveWidgetAsData.widgetName;
this.widget.alias = undefined; this.widget.fullFqn = undefined;
const config = JSON.parse(this.widget.defaultConfig); const config = JSON.parse(this.widget.defaultConfig);
config.title = this.widget.widgetName; config.title = this.widget.widgetName;
this.widget.defaultConfig = JSON.stringify(config); this.widget.defaultConfig = JSON.stringify(config);

10
ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts

@ -65,9 +65,7 @@ export class WidgetsTypesDataResolver implements Resolve<WidgetsData> {
const isSystem = widgetsBundle.tenantId.id === NULL_UUID; const isSystem = widgetsBundle.tenantId.id === NULL_UUID;
return this.widgetsService.loadBundleLibraryWidgets(bundleAlias, return this.widgetsService.loadBundleLibraryWidgets(bundleAlias,
isSystem).pipe( isSystem).pipe(
map((widgets) => { map((widgets) => ({ widgets })
return { widgets };
}
)); ));
} }
} }
@ -81,12 +79,10 @@ export class WidgetEditorDataResolver implements Resolve<WidgetEditorData> {
resolve(route: ActivatedRouteSnapshot): Observable<WidgetEditorData> { resolve(route: ActivatedRouteSnapshot): Observable<WidgetEditorData> {
const widgetTypeId = route.params.widgetTypeId; const widgetTypeId = route.params.widgetTypeId;
return this.widgetsService.getWidgetTypeById(widgetTypeId).pipe( return this.widgetsService.getWidgetTypeById(widgetTypeId).pipe(
map((result) => { map((result) => ({
return {
widgetTypeDetails: result, widgetTypeDetails: result,
widget: detailsToWidgetInfo(result) widget: detailsToWidgetInfo(result)
}; }))
})
); );
} }
} }

11
ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts

@ -86,12 +86,9 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit {
aliasController: IAliasController = new AliasController(this.utils, aliasController: IAliasController = new AliasController(this.utils,
this.entityService, this.entityService,
this.translate, this.translate,
() => { return { () => ({
getStateParams(): StateParams { getStateParams: (): StateParams => ({})
return {}; } as IStateController),
}
} as IStateController;
},
{}, {},
{}); {});
@ -189,7 +186,7 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit {
).pipe( ).pipe(
mergeMap((result) => { mergeMap((result) => {
if (result) { if (result) {
return this.widgetService.deleteWidgetType(widget.bundleAlias, widget.typeAlias, widget.isSystemType); return this.widgetService.deleteWidgetType(widget.typeFullFqn);
} else { } else {
return of(false); return of(false);
} }

25
ui-ngx/src/app/shared/models/dashboard.models.ts

@ -74,10 +74,10 @@ export interface DashboardLayoutInfo {
} }
export interface LayoutDimension { export interface LayoutDimension {
type?: LayoutType, type?: LayoutType;
fixedWidth?: number, fixedWidth?: number;
fixedLayout?: DashboardLayoutId, fixedLayout?: DashboardLayoutId;
leftWidthPercentage?: number leftWidthPercentage?: number;
} }
export declare type DashboardLayoutId = 'main' | 'right'; export declare type DashboardLayoutId = 'main' | 'right';
@ -137,16 +137,16 @@ export interface HomeDashboardInfo {
hideDashboardToolbar: boolean; hideDashboardToolbar: boolean;
} }
export function isPublicDashboard(dashboard: DashboardInfo): boolean { export const isPublicDashboard = (dashboard: DashboardInfo): boolean => {
if (dashboard && dashboard.assignedCustomers) { if (dashboard && dashboard.assignedCustomers) {
return dashboard.assignedCustomers return dashboard.assignedCustomers
.filter(customerInfo => customerInfo.public).length > 0; .filter(customerInfo => customerInfo.public).length > 0;
} else { } else {
return false; return false;
} }
} };
export function getDashboardAssignedCustomersText(dashboard: DashboardInfo): string { export const getDashboardAssignedCustomersText = (dashboard: DashboardInfo): string => {
if (dashboard && dashboard.assignedCustomers && dashboard.assignedCustomers.length > 0) { if (dashboard && dashboard.assignedCustomers && dashboard.assignedCustomers.length > 0) {
return dashboard.assignedCustomers return dashboard.assignedCustomers
.filter(customerInfo => !customerInfo.public) .filter(customerInfo => !customerInfo.public)
@ -155,14 +155,13 @@ export function getDashboardAssignedCustomersText(dashboard: DashboardInfo): str
} else { } else {
return ''; return '';
} }
} };
export function isCurrentPublicDashboardCustomer(dashboard: DashboardInfo, customerId: string): boolean { export const isCurrentPublicDashboardCustomer = (dashboard: DashboardInfo, customerId: string): boolean => {
if (customerId && dashboard && dashboard.assignedCustomers) { if (customerId && dashboard && dashboard.assignedCustomers) {
return dashboard.assignedCustomers.filter(customerInfo => { return dashboard.assignedCustomers.filter(customerInfo =>
return customerInfo.public && customerId === customerInfo.customerId.id; customerInfo.public && customerId === customerInfo.customerId.id).length > 0;
}).length > 0;
} else { } else {
return false; return false;
} }
} };

55
ui-ngx/src/app/shared/models/widget.models.ts

@ -38,9 +38,10 @@ import { AbstractControl, UntypedFormGroup } from '@angular/forms';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { Dashboard } from '@shared/models/dashboard.models'; import { Dashboard } from '@shared/models/dashboard.models';
import { IAliasController } from '@core/api/widget-api.models'; import { IAliasController } from '@core/api/widget-api.models';
import { isEmptyStr } from '@core/utils'; import { isEmptyStr, isNotEmptyStr } from '@core/utils';
import { WidgetConfigComponentData } from '@home/models/widget-component.models'; import { WidgetConfigComponentData } from '@home/models/widget-component.models';
import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models'; import { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models';
import { NULL_UUID } from '@shared/models/id/has-uuid';
export enum widgetType { export enum widgetType {
timeseries = 'timeseries', timeseries = 'timeseries',
@ -51,8 +52,7 @@ export enum widgetType {
} }
export interface WidgetTypeTemplate { export interface WidgetTypeTemplate {
bundleAlias: string; fullFqn: string;
alias: string;
} }
export interface WidgetTypeData { export interface WidgetTypeData {
@ -71,8 +71,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>(
icon: 'timeline', icon: 'timeline',
configHelpLinkId: 'widgetsConfigTimeseries', configHelpLinkId: 'widgetsConfigTimeseries',
template: { template: {
bundleAlias: 'charts', fullFqn: 'system.charts.basic_timeseries'
alias: 'basic_timeseries'
} }
} }
], ],
@ -83,8 +82,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>(
icon: 'track_changes', icon: 'track_changes',
configHelpLinkId: 'widgetsConfigLatest', configHelpLinkId: 'widgetsConfigLatest',
template: { template: {
bundleAlias: 'cards', fullFqn: 'system.cards.attributes_card'
alias: 'attributes_card'
} }
} }
], ],
@ -95,8 +93,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>(
icon: 'mdi:developer-board', icon: 'mdi:developer-board',
configHelpLinkId: 'widgetsConfigRpc', configHelpLinkId: 'widgetsConfigRpc',
template: { template: {
bundleAlias: 'gpio_widgets', fullFqn: 'system.gpio_widgets.basic_gpio_control'
alias: 'basic_gpio_control'
} }
} }
], ],
@ -107,8 +104,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>(
icon: 'error', icon: 'error',
configHelpLinkId: 'widgetsConfigAlarm', configHelpLinkId: 'widgetsConfigAlarm',
template: { template: {
bundleAlias: 'alarm_widgets', fullFqn: 'system.alarm_widgets.alarms_table'
alias: 'alarms_table'
} }
} }
], ],
@ -119,8 +115,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>(
icon: 'font_download', icon: 'font_download',
configHelpLinkId: 'widgetsConfigStatic', configHelpLinkId: 'widgetsConfigStatic',
template: { template: {
bundleAlias: 'cards', fullFqn: 'system.cards.html_card'
alias: 'html_card'
} }
} }
] ]
@ -198,10 +193,38 @@ export interface WidgetControllerDescriptor {
export interface BaseWidgetType extends BaseData<WidgetTypeId> { export interface BaseWidgetType extends BaseData<WidgetTypeId> {
tenantId: TenantId; tenantId: TenantId;
bundleAlias: string; bundleAlias: string;
alias: string; fqn: string;
name: string; name: string;
deprecated: boolean;
} }
export const fullWidgetTypeFqn = (type: BaseWidgetType): string =>
((!type.tenantId || type.tenantId?.id === NULL_UUID) ? 'system' : 'tenant') + '.' + type.fqn;
export const widgetTypeFqn = (fullFqn: string): string => {
if (isNotEmptyStr(fullFqn)) {
const parts = fullFqn.split('.');
if (parts.length > 1) {
const scopeQualifier = parts[0];
if (['system', 'tenant'].includes(scopeQualifier)) {
return fullFqn.substring(scopeQualifier.length + 1);
}
}
}
return fullFqn;
};
export const isValidWidgetFullFqn = (fullFqn: string): boolean => {
if (isNotEmptyStr(fullFqn)) {
const parts = fullFqn.split('.');
if (parts.length > 1) {
const scopeQualifier = parts[0];
return ['system', 'tenant'].includes(scopeQualifier);
}
}
return false;
};
export interface WidgetType extends BaseWidgetType { export interface WidgetType extends BaseWidgetType {
descriptor: WidgetTypeDescriptor; descriptor: WidgetTypeDescriptor;
} }
@ -670,9 +693,7 @@ export interface Widget extends WidgetInfo{
export interface WidgetInfo { export interface WidgetInfo {
id?: string; id?: string;
isSystemType: boolean; typeFullFqn: string;
bundleAlias: string;
typeAlias: string;
type: widgetType; type: widgetType;
title: string; title: string;
image?: string; image?: string;

Loading…
Cancel
Save