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",
"title": "Date",
"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": [
{
"alias": "date_range_navigator",
"name": "Date-range-navigator",
"image": "data:image/png;base64,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",
"description": "Allows to change the data range for other widgets on the dashboard.",
@ -23,7 +24,9 @@
"dataKeySettingsSchema": "{}\n",
"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\":{}}"
}
},
"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;
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.ResponseStatus;
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.id.TenantId;
import org.thingsboard.server.common.data.id.WidgetTypeId;
@ -206,10 +207,59 @@ public class WidgetTypeController extends AutoCommitController {
} else {
tenantId = getCurrentUser().getTenantId();
}
WidgetType widgetType = widgetTypeService.findWidgetTypeByTenantIdBundleAliasAndAlias(tenantId, bundleAlias, alias);
WidgetType widgetType = widgetTypeService.findWidgetTypeByTenantIdAndFqn(tenantId, bundleAlias + "." + alias);
checkNotNull(widgetType);
accessControlService.checkPermission(getCurrentUser(), Resource.WIDGET_TYPE, Operation.READ, widgetType.getId(), 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) {
builder.setBundleAlias(widgetTypeDetails.getBundleAlias());
}
if (widgetTypeDetails.getAlias() != null) {
builder.setAlias(widgetTypeDetails.getAlias());
if (widgetTypeDetails.getFqn() != null) {
builder.setFqn(widgetTypeDetails.getFqn());
}
if (widgetTypeDetails.getName() != null) {
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 {
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()) {
WidgetTypeInfo existingWidget;
if ((existingWidget = existingWidgets.remove(widget.getAlias())) != null) {
if ((existingWidget = existingWidgets.remove(widget.getFqn())) != null) {
widget.setId(existingWidget.getId());
widget.setCreatedTime(existingWidget.getCreatedTime());
} 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.getId());
Assert.assertNotNull(savedWidgetType.getAlias());
Assert.assertNotNull(savedWidgetType.getFqn());
Assert.assertTrue(savedWidgetType.getCreatedTime() > 0);
Assert.assertEquals(savedTenant.getId(), savedWidgetType.getTenantId());
Assert.assertEquals(widgetType.getName(), savedWidgetType.getName());
@ -199,16 +199,16 @@ public class WidgetTypeControllerTest extends AbstractControllerTest {
}
@Test
public void testUpdateWidgetTypeAlias() throws Exception {
public void testUpdateWidgetTypeFqn() throws Exception {
WidgetTypeDetails widgetType = new WidgetTypeDetails();
widgetType.setBundleAlias(savedWidgetsBundle.getAlias());
widgetType.setName("Widget Type");
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class);
savedWidgetType.setAlias("some_alias");
savedWidgetType.setFqn("some_fqn");
doPost("/api/widgetType", savedWidgetType)
.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.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
WidgetTypeDetails savedWidgetType = doPost("/api/widgetType", widgetType, WidgetTypeDetails.class);
WidgetType foundWidgetType = doGet("/api/widgetType?isSystem={isSystem}&bundleAlias={bundleAlias}&alias={alias}",
WidgetType.class, false, savedWidgetsBundle.getAlias(), savedWidgetType.getAlias());
WidgetType foundWidgetType = doGet("/api/widgetType?fqn={fqn}",
WidgetType.class, "tenant."+savedWidgetType.getFqn());
Assert.assertNotNull(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(savedWidgetType.getUuidId().getMostSignificantBits(), widgetTypeUpdateMsg.getIdMSB());
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(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 setWidgetTypeDeprecated(TenantId tenantId, WidgetTypeId widgetTypeId, boolean deprecated);
List<WidgetType> findWidgetTypesByTenantIdAndBundleAlias(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);
WidgetType findWidgetTypeByTenantIdBundleAliasAndAlias(TenantId tenantId, String bundleAlias, String alias);
WidgetType findWidgetTypeByTenantIdAndFqn(TenantId tenantId, String fqn);
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 MQTT_TOPIC = "mqttTopic";
public static final String MAIN_QUEUE_NAME = "Main";
public static final String MAIN_QUEUE_TOPIC = "tb_rule_engine.main";
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
public EntityExportData<WidgetsBundle> sort() {
super.sort();
widgets.sort(Comparator.comparing(BaseWidgetType::getAlias));
widgets.sort(Comparator.comparing(BaseWidgetType::getFqn));
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)
private String bundleAlias;
@NoXss
@Length(fieldName = "alias")
@ApiModelProperty(position = 5, value = "Unique alias that is used in dashboards as a reference widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String alias;
@Length(fieldName = "fqn")
@ApiModelProperty(position = 5, value = "Unique FQN that is used in dashboards as a reference widget type", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String fqn;
@NoXss
@Length(fieldName = "name")
@ApiModelProperty(position = 6, value = "Widget name used in search and UI", accessMode = ApiModelProperty.AccessMode.READ_ONLY)
private String name;
@ApiModelProperty(position = 7, value = "Whether widget type is deprecated.", example = "true")
private boolean deprecated;
public BaseWidgetType() {
super();
}
@ -57,8 +60,9 @@ public class BaseWidgetType extends BaseData<WidgetTypeId> implements HasName, H
super(widgetType);
this.tenantId = widgetType.getTenantId();
this.bundleAlias = widgetType.getBundleAlias();
this.alias = widgetType.getAlias();
this.fqn = widgetType.getFqn();
this.name = widgetType.getName();
this.deprecated = widgetType.isDeprecated();
}
@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
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;
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 {
@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;
@NoXss
@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;
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
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;
@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;
@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;
public WidgetTypeInfo() {

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

@ -362,7 +362,7 @@ message WidgetTypeUpdateMsg {
int64 idMSB = 2;
int64 idLSB = 3;
optional string bundleAlias = 4;
optional string alias = 5;
optional string fqn = 5;
optional string name = 6;
optional string descriptorJson = 7;
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.rpc.RpcStatus;
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.transport.SessionMsgListener;
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 final UUID sessionId;
protected final MqttTransportContext context;
private final TransportService transportService;
private final SchedulerComponent scheduler;
@ -442,10 +444,12 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
MqttTransportAdaptor payloadAdaptor = deviceSessionCtx.getPayloadAdaptor();
if (deviceSessionCtx.isDeviceAttributesTopic(topicName)) {
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)) {
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)) {
TransportProtos.GetAttributeRequestMsg getAttributeMsg = payloadAdaptor.convertToGetAttributes(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_ATTRIBUTES_REQUEST_TOPIC_PREFIX);
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);
} else if (topicName.equals(MqttTopics.DEVICE_TELEMETRY_SHORT_TOPIC)) {
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)) {
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)) {
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)) {
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)) {
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)) {
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)) {
TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = context.getJsonMqttAdaptor().convertToDeviceRpcResponse(deviceSessionCtx, mqttMsg, MqttTopics.DEVICE_RPC_RESPONSE_SHORT_JSON_TOPIC);
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) {
if ((deviceSessionCtx.isSendAckOnValidationException() || MqttVersion.MQTT_5.equals(deviceSessionCtx.getMqttVersion())) && msgId > 0) {
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 sendAckOnValidationException;
@Getter
private volatile boolean deviceProfileMqttTransportType;
@Getter
@Setter
private TransportPayloadType provisionPayloadType = payloadType;
public DeviceSessionCtx(UUID sessionId, ConcurrentMap<MqttTopicMatcher, Integer> mqttQoSMap, MqttTransportContext context) {
super(sessionId, mqttQoSMap);
this.context = context;
@ -165,6 +169,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
MqttDeviceProfileTransportConfiguration mqttConfig = (MqttDeviceProfileTransportConfiguration) transportConfiguration;
TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttConfig.getTransportPayloadTypeConfiguration();
payloadType = transportPayloadTypeConfiguration.getTransportPayloadType();
deviceProfileMqttTransportType = true;
telemetryTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceTelemetryTopic());
attributesPublishTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesTopic());
attributesSubscribeTopicFilter = MqttTopicFilterFactory.toFilter(mqttConfig.getDeviceAttributesSubscribeTopic());
@ -179,6 +184,7 @@ public class DeviceSessionCtx extends MqttDeviceAwareSessionContext {
telemetryTopicFilter = MqttTopicFilterFactory.getDefaultTelemetryFilter();
attributesPublishTopicFilter = MqttTopicFilterFactory.getDefaultAttributesFilter();
payloadType = TransportPayloadType.JSON;
deviceProfileMqttTransportType = false;
sendAckOnValidationException = false;
}
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;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.EmptyByteBuf;
import io.netty.buffer.PooledByteBufAllocator;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.mqtt.MqttConnectMessage;
import io.netty.handler.codec.mqtt.MqttConnectPayload;
@ -34,8 +33,19 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnitRunner;
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.nio.charset.StandardCharsets;
@ -55,12 +65,14 @@ import static org.hamcrest.Matchers.greaterThan;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.willDoNothing;
import static org.mockito.BDDMockito.willReturn;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@Slf4j
@RunWith(MockitoJUnitRunner.class)
@ -81,9 +93,14 @@ public class MqttTransportHandlerTest {
ExecutorService executor;
MqttTransportHandler handler;
@Spy
TransportService transportService;
@Before
public void setUp() throws Exception {
willReturn(MSG_QUEUE_LIMIT).given(context).getMessageQueueSizePerDeviceLimit();
willReturn(transportService).given(context).getTransportService();
handler = spy(new MqttTransportHandler(context, sslHandler));
willReturn(IP_ADDR).given(handler).getAddress(any());
@ -104,9 +121,17 @@ public class MqttTransportHandlerTest {
}
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);
MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader("v1/gateway/telemetry", packedId.incrementAndGet());
ByteBuf payload = new EmptyByteBuf(new PooledByteBufAllocator());
MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topicName, packedId.incrementAndGet());
ByteBuf payload = Unpooled.wrappedBuffer("{\"testKey\":\"testValue\"}".getBytes());
return new MqttPublishMessage(mqttFixedHeader, variableHeader, payload);
}
@ -205,4 +230,26 @@ public class MqttTransportHandlerTest {
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.DeviceTransportType;
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.ValidateDeviceCredentialsResponse;
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, TbMsgMetaData md, 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, 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
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;
for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) {
dataPoints += tsKv.getKvCount();
@ -578,7 +583,7 @@ public class DefaultTransportService implements TransportService {
CustomerId customerId = getCustomerId(sessionInfo);
MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), new ApiStatsProxyCallback<>(tenantId, customerId, dataPoints, callback));
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("deviceType", sessionInfo.getDeviceType());
metaData.putValue("ts", tsKv.getTs() + "");
@ -590,12 +595,17 @@ public class DefaultTransportService implements TransportService {
@Override
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())) {
reportActivityInternal(sessionInfo);
TenantId tenantId = getTenantId(sessionInfo);
DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()));
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("deviceType", sessionInfo.getDeviceType());
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_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY;
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_IMAGE_PROPERTY = "image";
public static final String WIDGET_TYPE_DESCRIPTION_PROPERTY = "description";
public static final String WIDGET_TYPE_DESCRIPTOR_PROPERTY = "descriptor";
public static final String WIDGET_TYPE_DEPRECATED_PROPERTY = "deprecated";
/**
* 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)
private String bundleAlias;
@Column(name = ModelConstants.WIDGET_TYPE_ALIAS_PROPERTY)
private String alias;
@Column(name = ModelConstants.WIDGET_TYPE_FQN_PROPERTY)
private String fqn;
@Column(name = ModelConstants.WIDGET_TYPE_NAME_PROPERTY)
private String name;
@Column(name = ModelConstants.WIDGET_TYPE_DEPRECATED_PROPERTY)
private boolean deprecated;
public AbstractWidgetTypeEntity() {
super();
}
@ -58,8 +61,9 @@ public abstract class AbstractWidgetTypeEntity<T extends BaseWidgetType> extends
this.tenantId = widgetType.getTenantId().getId();
}
this.bundleAlias = widgetType.getBundleAlias();
this.alias = widgetType.getAlias();
this.fqn = widgetType.getFqn();
this.name = widgetType.getName();
this.deprecated = widgetType.isDeprecated();
}
public AbstractWidgetTypeEntity(AbstractWidgetTypeEntity widgetTypeEntity) {
@ -67,8 +71,9 @@ public abstract class AbstractWidgetTypeEntity<T extends BaseWidgetType> extends
this.setCreatedTime(widgetTypeEntity.getCreatedTime());
this.tenantId = widgetTypeEntity.getTenantId();
this.bundleAlias = widgetTypeEntity.getBundleAlias();
this.alias = widgetTypeEntity.getAlias();
this.fqn = widgetTypeEntity.getFqn();
this.name = widgetTypeEntity.getName();
this.deprecated = widgetTypeEntity.isDeprecated();
}
protected BaseWidgetType toBaseWidgetType() {
@ -78,8 +83,9 @@ public abstract class AbstractWidgetTypeEntity<T extends BaseWidgetType> extends
widgetType.setTenantId(TenantId.fromUUID(tenantId));
}
widgetType.setBundleAlias(bundleAlias);
widgetType.setAlias(alias);
widgetType.setFqn(fqn);
widgetType.setName(name);
widgetType.setDeprecated(deprecated);
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) {
throw new DataValidationException("Widget type is referencing to non-existent widgets bundle!");
}
String alias = widgetTypeDetails.getAlias();
if (alias == null || alias.trim().isEmpty()) {
alias = widgetTypeDetails.getName().toLowerCase().replaceAll("\\W+", "_");
String fqn = widgetTypeDetails.getFqn();
if (fqn == null || fqn.trim().isEmpty()) {
fqn = widgetTypeDetails.getName().toLowerCase().replaceAll("\\W+", "_");
}
String originalAlias = alias;
String originalFqn = fqn;
int c = 1;
WidgetType withSameAlias;
WidgetType withSameFqn;
do {
withSameAlias = widgetTypeDao.findByTenantIdBundleAliasAndAlias(widgetTypeDetails.getTenantId().getId(), widgetTypeDetails.getBundleAlias(), alias);
if (withSameAlias != null) {
alias = originalAlias + (++c);
withSameFqn = widgetTypeDao.findByTenantIdAndFqn(widgetTypeDetails.getTenantId().getId(), fqn);
if (withSameFqn != null) {
fqn = originalFqn + (++c);
}
} while (withSameAlias != null);
widgetTypeDetails.setAlias(alias);
} while (withSameFqn != null);
widgetTypeDetails.setFqn(fqn);
}
@Override
@ -89,8 +89,8 @@ public class WidgetTypeDataValidator extends DataValidator<WidgetTypeDetails> {
if (!storedWidgetType.getBundleAlias().equals(widgetTypeDetails.getBundleAlias())) {
throw new DataValidationException("Update of widget type bundle alias is prohibited!");
}
if (!storedWidgetType.getAlias().equals(widgetTypeDetails.getAlias())) {
throw new DataValidationException("Update of widget type alias is prohibited!");
if (!storedWidgetType.getFqn().equals(widgetTypeDetails.getFqn())) {
throw new DataValidationException("Update of widget type fqn is prohibited!");
}
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
public WidgetType findByTenantIdBundleAliasAndAlias(UUID tenantId, String bundleAlias, String alias) {
return DaoUtil.getData(widgetTypeRepository.findWidgetTypeByTenantIdAndBundleAliasAndAlias(tenantId, bundleAlias, alias));
public WidgetType findByTenantIdAndFqn(UUID tenantId, String fqn) {
return DaoUtil.getData(widgetTypeRepository.findWidgetTypeByTenantIdAndFqn(tenantId, fqn));
}
@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);
@Query("SELECT wt FROM WidgetTypeEntity wt " +
"WHERE wt.tenantId = :tenantId AND wt.bundleAlias = :bundleAlias AND wt.alias = :alias")
WidgetTypeEntity findWidgetTypeByTenantIdAndBundleAliasAndAlias(@Param("tenantId") UUID tenantId,
@Param("bundleAlias") String bundleAlias,
@Param("alias") String alias);
"WHERE wt.tenantId = :tenantId AND wt.fqn = :fqn")
WidgetTypeEntity findWidgetTypeByTenantIdAndFqn(@Param("tenantId") UUID tenantId,
@Param("fqn") String fqn);
@Query(value = "SELECT * FROM widget_type wt " +
"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);
/**
* Find widget type by tenantId, bundleAlias and alias.
* Find widget type by tenantId and FQN.
*
* @param tenantId the tenantId
* @param bundleAlias the bundle alias
* @param alias the alias
* @param fqn the FQN
* @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.

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.WidgetTypeDetails;
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.SaveEntityEvent;
import org.thingsboard.server.dao.service.DataValidator;
@ -38,7 +39,7 @@ import java.util.Optional;
@Service("WidgetTypeDaoService")
@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_RESOURCE_ID = "Incorrect resourceId ";
@ -71,10 +72,16 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
public WidgetTypeDetails saveWidgetType(WidgetTypeDetails widgetTypeDetails) {
log.trace("Executing saveWidgetType [{}]", widgetTypeDetails);
widgetTypeValidator.validate(widgetTypeDetails, WidgetType::getTenantId);
WidgetTypeDetails result = widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId())
.entityId(result.getId()).added(widgetTypeDetails.getId() == null).build());
return result;
try {
WidgetTypeDetails result = widgetTypeDao.save(widgetTypeDetails.getTenantId(), widgetTypeDetails);
eventPublisher.publishEvent(SaveEntityEvent.builder().tenantId(result.getTenantId())
.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
@ -85,6 +92,17 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
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
public List<WidgetType> findWidgetTypesByTenantIdAndBundleAlias(TenantId tenantId, String bundleAlias) {
log.trace("Executing findWidgetTypesByTenantIdAndBundleAlias, tenantId [{}], bundleAlias [{}]", tenantId, bundleAlias);
@ -118,12 +136,11 @@ public class WidgetTypeServiceImpl implements WidgetTypeService {
}
@Override
public WidgetType findWidgetTypeByTenantIdBundleAliasAndAlias(TenantId tenantId, String bundleAlias, String alias) {
log.trace("Executing findWidgetTypeByTenantIdBundleAliasAndAlias, tenantId [{}], bundleAlias [{}], alias [{}]", tenantId, bundleAlias, alias);
public WidgetType findWidgetTypeByTenantIdAndFqn(TenantId tenantId, String fqn) {
log.trace("Executing findWidgetTypeByTenantIdAndFqn, tenantId [{}], fqn [{}]", tenantId, fqn);
Validator.validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
Validator.validateString(bundleAlias, INCORRECT_BUNDLE_ALIAS + bundleAlias);
Validator.validateString(alias, "Incorrect alias " + alias);
return widgetTypeDao.findByTenantIdBundleAliasAndAlias(tenantId.getId(), bundleAlias, alias);
Validator.validateString(fqn, "Incorrect fqn " + fqn);
return widgetTypeDao.findByTenantIdAndFqn(tenantId.getId(), fqn);
}
@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 (
id uuid NOT NULL CONSTRAINT widget_type_pkey PRIMARY KEY,
created_time bigint NOT NULL,
alias varchar(255),
fqn varchar(512),
bundle_alias varchar(255),
descriptor varchar(1000000),
name varchar(255),
tenant_id uuid,
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 (

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.getId());
Assert.assertNotNull(savedWidgetType.getAlias());
Assert.assertNotNull(savedWidgetType.getFqn());
Assert.assertTrue(savedWidgetType.getCreatedTime() > 0);
Assert.assertEquals(widgetType.getTenantId(), savedWidgetType.getTenantId());
Assert.assertEquals(widgetType.getName(), savedWidgetType.getName());
@ -211,7 +211,7 @@ public class WidgetTypeServiceTest extends AbstractServiceTest {
}
@Test
public void testUpdateWidgetTypeAlias() throws IOException {
public void testUpdateWidgetTypeFqn() throws IOException {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTenantId(tenantId);
widgetsBundle.setTitle("Widgets bundle");
@ -223,7 +223,7 @@ public class WidgetTypeServiceTest extends AbstractServiceTest {
widgetType.setName("Widget Type");
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
WidgetTypeDetails savedWidgetType = widgetTypeService.saveWidgetType(widgetType);
savedWidgetType.setAlias("some_alias");
savedWidgetType.setFqn("some_fqn");
try {
Assertions.assertThrows(DataValidationException.class, () -> {
widgetTypeService.saveWidgetType(savedWidgetType);
@ -254,7 +254,7 @@ public class WidgetTypeServiceTest extends AbstractServiceTest {
}
@Test
public void testFindWidgetTypeByTenantIdBundleAliasAndAlias() throws IOException {
public void testFindWidgetTypeByTenantIdAndFqn() throws IOException {
WidgetsBundle widgetsBundle = new WidgetsBundle();
widgetsBundle.setTenantId(tenantId);
widgetsBundle.setTitle("Widgets bundle");
@ -266,7 +266,7 @@ public class WidgetTypeServiceTest extends AbstractServiceTest {
widgetType.setName("Widget Type");
widgetType.setDescriptor(JacksonUtil.fromString("{ \"someKey\": \"someValue\" }", JsonNode.class));
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.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();
widgetType.setTenantId(TenantId.SYS_TENANT_ID);
widgetType.setName("WIDGET_TYPE_" + number);
widgetType.setAlias("ALIAS_" + number);
widgetType.setFqn("FQN_" + number);
widgetType.setBundleAlias(BUNDLE_ALIAS);
return widgetTypeDao.save(TenantId.SYS_TENANT_ID, widgetType);
}
@ -74,10 +74,10 @@ public class JpaWidgetTypeDaoTest extends AbstractJpaDaoTest {
}
@Test
public void testFindByTenantIdAndBundleAliasAndAlias() {
public void testFindByTenantIdAndFqn() {
WidgetType result = widgetTypeList.get(0);
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());
}
}

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.google.common.util.concurrent.Futures;
import lombok.extern.slf4j.Slf4j;
import org.awaitility.Awaitility;
import org.junit.Assert;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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.Mock;
import org.mockito.Mockito;
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.JacksonUtil;
import org.thingsboard.rule.engine.api.RuleEngineTelemetryService;
@ -55,25 +53,31 @@ import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
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.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyDouble;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.argThat;
import static org.mockito.ArgumentMatchers.eq;
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.spy;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@Slf4j
@ExtendWith(MockitoExtension.class)
@ -81,10 +85,11 @@ public class TbMathNodeTest {
static final int RULE_DISPATCHER_POOL_SIZE = 2;
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 TenantId tenantId = TenantId.fromUUID(UUID.fromString("e7f46b23-0c7d-42f5-9b06-fc35ab17af8a"));
@Mock
@Mock(lenient = true)
private TbContext ctx;
@Mock
private AttributesService attributesService;
@ -102,11 +107,11 @@ public class TbMathNodeTest {
ruleEngineDispatcherExecutor = new RuleDispatcherExecutor();
ruleEngineDispatcherExecutor.init();
lenient().when(ctx.getAttributesService()).thenReturn(attributesService);
lenient().when(ctx.getTelemetryService()).thenReturn(telemetryService);
lenient().when(ctx.getTimeseriesService()).thenReturn(tsService);
lenient().when(ctx.getTenantId()).thenReturn(tenantId);
lenient().when(ctx.getDbCallbackExecutor()).thenReturn(dbCallbackExecutor);
willReturn(dbCallbackExecutor).given(ctx).getDbCallbackExecutor();
willReturn(attributesService).given(ctx).getAttributesService();
willReturn(telemetryService).given(ctx).getTelemetryService();
willReturn(tsService).given(ctx).getTimeseriesService();
willReturn(tenantId).given(ctx).getTenantId();
}
@AfterEach
@ -115,10 +120,6 @@ public class TbMathNodeTest {
dbCallbackExecutor.executor().shutdownNow();
}
private void initMocks() {
Mockito.clearInvocations(ctx, attributesService, tsService, telemetryService);
}
private TbMathNode initNode(TbRuleNodeMathFunctionType operation, TbMathResult result, TbMathArgument... arguments) {
return initNode(operation, null, result, arguments);
}
@ -161,9 +162,6 @@ public class TbMathNodeTest {
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("key2", "argumentC");
msgNode = JacksonUtil.newObjectNode()
@ -172,80 +170,43 @@ public class TbMathNodeTest {
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);
Mockito.verify(ctx, Mockito.times(2)).tellSuccess(msgCaptor.capture());
verify(ctx, timeout(TIMEOUT).times(2)).tellSuccess(msgCaptor.capture());
List<TbMsg> resultMsgs = msgCaptor.getAllValues();
Assert.assertFalse(resultMsgs.isEmpty());
Assert.assertEquals(2, resultMsgs.size());
assertFalse(resultMsgs.isEmpty());
assertEquals(2, resultMsgs.size());
for (int i = 0; i < resultMsgs.size(); i++) {
TbMsg outMsg = resultMsgs.get(i);
Assert.assertNotNull(outMsg);
Assert.assertNotNull(outMsg.getData());
assertNotNull(outMsg);
assertNotNull(outMsg.getData());
var resultJson = JacksonUtil.toJsonNode(outMsg.getData());
String resultKey = i == 0 ? "firstMsgResult" : "secondMsgResult";
Assert.assertTrue(resultJson.has(resultKey));
Assert.assertEquals(i == 0 ? 10 : 17, resultJson.get(resultKey).asInt());
assertTrue(resultJson.has(resultKey));
assertEquals(i == 0 ? 10 : 17, resultJson.get(resultKey).asInt());
}
semaphores.remove(originator);
}
@Test
public void testSimpleFunctions() {
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.ADD, 2.1, 2.2, 4.3);
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.SUB, 2.1, 2.2, -0.1);
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.MULT, 2.1, 2.0, 4.2);
testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.DIV, 4.2, 2.0, 2.1);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SIN, Math.toRadians(30), 0.5);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SIN, Math.toRadians(90), 1.0);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SINH, Math.toRadians(0), 0.0);
testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.COSH, Math.toRadians(0), 1.0);
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 static Stream<Arguments> testSimpleTwoArgumentFunction() {
return Stream.of(
Arguments.of(TbRuleNodeMathFunctionType.ADD, 2.1, 2.2, 4.3),
Arguments.of(TbRuleNodeMathFunctionType.SUB, 2.1, 2.2, -0.1),
Arguments.of(TbRuleNodeMathFunctionType.MULT, 2.1, 2.0, 4.2),
Arguments.of(TbRuleNodeMathFunctionType.DIV, 4.2, 2.0, 2.1),
Arguments.of(TbRuleNodeMathFunctionType.ATAN2, 0.5, 0.3, 1.03),
Arguments.of(TbRuleNodeMathFunctionType.HYPOT, 4, 5, 6.4),
Arguments.of(TbRuleNodeMathFunctionType.FLOOR_DIV, 5, 3, 1),
Arguments.of(TbRuleNodeMathFunctionType.FLOOR_MOD, 6, 3, 0),
Arguments.of(TbRuleNodeMathFunctionType.MIN, 5, 3, 3),
Arguments.of(TbRuleNodeMathFunctionType.MAX, 5, 3, 5),
Arguments.of(TbRuleNodeMathFunctionType.POW, 5, 3, 125)
);
}
private void testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType function, double arg1, double arg2, double result) {
initMocks();
@ParameterizedTest
@MethodSource
public void testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType function, double arg1, double arg2, double result) {
var node = initNode(function,
new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null),
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a"),
@ -257,19 +218,59 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg);
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();
Assert.assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData());
assertNotNull(resultMsg);
assertNotNull(resultMsg.getData());
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultJson.has("result"));
Assert.assertEquals(result, resultJson.get("result").asDouble(), 0d);
assertTrue(resultJson.has("result"));
assertEquals(result, resultJson.get("result").asDouble(), 0d);
}
private void testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType function, double arg1, double result) {
initMocks();
private static Stream<Arguments> testSimpleOneArgumentFunction() {
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,
new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 2, false, false, null),
new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a")
@ -280,14 +281,14 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg);
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();
Assert.assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData());
assertNotNull(resultMsg);
assertNotNull(resultMsg.getData());
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultJson.has("result"));
Assert.assertEquals(result, resultJson.get("result").asDouble(), 0d);
assertTrue(resultJson.has("result"));
assertEquals(result, resultJson.get("result").asDouble(), 0d);
}
@Test
@ -303,14 +304,14 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg);
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();
Assert.assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData());
assertNotNull(resultMsg);
assertNotNull(resultMsg.getData());
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultJson.has("result"));
Assert.assertEquals(4, resultJson.get("result").asInt());
assertTrue(resultJson.has("result"));
assertEquals(4, resultJson.get("result").asInt());
}
@Test
@ -326,15 +327,15 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg);
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();
Assert.assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData());
Assert.assertNotNull(resultMsg.getMetaData());
assertNotNull(resultMsg);
assertNotNull(resultMsg.getData());
assertNotNull(resultMsg.getMetaData());
var result = resultMsg.getMetaData().getValue("result");
Assert.assertNotNull(result);
Assert.assertEquals("4", result);
assertNotNull(result);
assertEquals("4", result);
}
@Test
@ -347,23 +348,23 @@ public class TbMathNodeTest {
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)))));
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)))));
node.onMsg(ctx, msg);
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();
Assert.assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData());
assertNotNull(resultMsg);
assertNotNull(resultMsg.getData());
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultJson.has("result"));
Assert.assertEquals(4, resultJson.get("result").asInt());
assertTrue(resultJson.has("result"));
assertEquals(4, resultJson.get("result").asInt());
}
@Test
@ -378,14 +379,14 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg);
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();
Assert.assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData());
assertNotNull(resultMsg);
assertNotNull(resultMsg.getData());
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultJson.has("result"));
Assert.assertEquals(2.236, resultJson.get("result").asDouble(), 0.0);
assertTrue(resultJson.has("result"));
assertEquals(2.236, resultJson.get("result").asDouble(), 0.0);
}
@Test
@ -400,14 +401,14 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg);
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();
Assert.assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData());
assertNotNull(resultMsg);
assertNotNull(resultMsg.getData());
var result = resultMsg.getMetaData().getValue("result");
Assert.assertNotNull(result);
Assert.assertEquals("2.236", result);
assertNotNull(result);
assertEquals("2.236", result);
}
@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());
Mockito.when(telemetryService.saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble()))
when(telemetryService.saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble()))
.thenReturn(Futures.immediateFuture(null));
node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture());
Mockito.verify(telemetryService, times(1)).saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble());
verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
verify(telemetryService, times(1)).saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble());
TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData());
assertNotNull(resultMsg);
assertNotNull(resultMsg.getData());
var result = resultMsg.getMetaData().getValue("result");
Assert.assertNotNull(result);
Assert.assertEquals("2.236", result);
assertNotNull(result);
assertEquals("2.236", result);
}
@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());
Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class)))
when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class)))
.thenReturn(Futures.immediateFuture(null));
node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture());
Mockito.verify(telemetryService, times(1)).saveAndNotify(any(), any(), any(TsKvEntry.class));
verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
verify(telemetryService, times(1)).saveAndNotify(any(), any(), any(TsKvEntry.class));
TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData());
assertNotNull(resultMsg);
assertNotNull(resultMsg.getData());
var resultJson = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultJson.has("result"));
Assert.assertEquals(2.236, resultJson.get("result").asDouble(), 0.0);
assertTrue(resultJson.has("result"));
assertEquals(2.236, resultJson.get("result").asDouble(), 0.0);
}
@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());
Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class)))
when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class)))
.thenReturn(Futures.immediateFuture(null));
node.onMsg(ctx, msg);
ArgumentCaptor<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture());
Mockito.verify(telemetryService, times(1)).saveAndNotify(any(), any(), any(TsKvEntry.class));
verify(ctx, timeout(TIMEOUT)).tellSuccess(msgCaptor.capture());
verify(telemetryService, times(1)).saveAndNotify(any(), any(), any(TsKvEntry.class));
TbMsg resultMsg = msgCaptor.getValue();
Assert.assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData());
assertNotNull(resultMsg);
assertNotNull(resultMsg.getData());
var resultMetadata = resultMsg.getMetaData().getValue("result");
var resultData = JacksonUtil.toJsonNode(resultMsg.getData());
Assert.assertTrue(resultData.has("result"));
Assert.assertEquals(2.236, resultData.get("result").asDouble(), 0.0);
assertTrue(resultData.has("result"));
assertEquals(2.236, resultData.get("result").asDouble(), 0.0);
Assert.assertNotNull(resultMetadata);
Assert.assertEquals("2.236", resultMetadata);
assertNotNull(resultMetadata);
assertEquals("2.236", resultMetadata);
}
@Test
@ -503,14 +504,14 @@ public class TbMathNodeTest {
node.onMsg(ctx, msg);
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();
Assert.assertNotNull(resultMsg);
Assert.assertNotNull(resultMsg.getData());
assertNotNull(resultMsg);
assertNotNull(resultMsg.getData());
var result = resultMsg.getMetaData().getValue("result");
Assert.assertNotNull(result);
Assert.assertEquals("2.236", result);
assertNotNull(result);
assertEquals("2.236", result);
}
@Test
@ -523,7 +524,7 @@ public class TbMathNodeTest {
Throwable thrown = assertThrows(RuntimeException.class, () -> {
node.onMsg(ctx, msg);
});
Assert.assertNotNull(thrown.getMessage());
assertNotNull(thrown.getMessage());
}
@Test
@ -537,7 +538,7 @@ public class TbMathNodeTest {
Throwable thrown = assertThrows(RuntimeException.class, () -> {
node.onMsg(ctx, msg);
});
Assert.assertNotNull(thrown.getMessage());
assertNotNull(thrown.getMessage());
}
@Test
@ -594,16 +595,16 @@ public class TbMathNodeTest {
// submit slow msg may block all rule engine dispatcher threads
slowMsgList.forEach(msg -> ruleEngineDispatcherExecutor.executeAsync(() -> node.onMsg(ctx, msg)));
// 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
fastMsgList.forEach(msg -> ruleEngineDispatcherExecutor.executeAsync(() -> node.onMsg(ctx, msg)));
// 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();
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());
}

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

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

@ -29,12 +29,20 @@ import {
GridSettings,
WidgetLayout
} 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 {
Datasource,
datasourcesHasOnlyComparisonAggregation,
DatasourceType,
defaultLegendConfig,
defaultLegendConfig, isValidWidgetFullFqn,
Widget,
WidgetConfig,
WidgetConfigMode,
@ -214,12 +222,11 @@ export class DashboardUtilsService {
public validateAndUpdateWidget(widget: Widget): Widget {
widget.config = this.validateAndUpdateWidgetConfig(widget.config, widget.type);
widget = this.validateAndUpdateWidgetTypeFqn(widget);
// Temp workaround
if (widget.isSystemType && widget.bundleAlias === 'charts' && widget.typeAlias === 'timeseries') {
widget.typeAlias = 'basic_timeseries';
}
if (widget.isSystemType && widget.bundleAlias === 'charts' &&
['state_chart', 'basic_timeseries', 'timeseries_bars_flot'].includes(widget.typeAlias)) {
if (['system.charts.state_chart',
'system.charts.basic_timeseries',
'system.charts.timeseries_bars_flot'].includes(widget.typeFullFqn)) {
const widgetConfig = widget.config;
const widgetSettings = widget.config.settings;
if (isDefinedAndNotNull(widgetConfig.showLegend)) {
@ -238,6 +245,20 @@ export class DashboardUtilsService {
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 {
if (!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 {
const editWidgetInfo = this.utils.editWidgetInfo;
const widget: Widget = {
isSystemType: true,
bundleAlias: 'customWidgetBundle',
typeAlias: 'customWidget',
typeFullFqn: 'system.customWidget',
type: editWidgetInfo.type,
title: 'My widget',
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
const stateController: IStateController = {
getStateParams(): StateParams {
return {};
}
getStateParams: (): StateParams => ({})
};
const filters: Filters = {};
@ -587,9 +585,7 @@ export class AttributeTableComponent extends PageComponent implements AfterViewI
const sizeY = widgetInfo.sizeY * 2;
const col = Math.floor(Math.max(0, (20 - sizeX) / 2));
const widget: Widget = {
isSystemType: isSystem,
bundleAlias,
typeAlias: widgetInfo.alias,
typeFullFqn: widgetInfo.fullFqn,
type: widgetInfo.type,
title: widgetInfo.widgetName,
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) {
this.onAddWidgetClosed();
this.searchBundle = '';
this.widgetComponentService.getWidgetInfo(widget.bundleAlias, widget.typeAlias, widget.isSystemType).subscribe(
this.widgetComponentService.getWidgetInfo(widget.typeFullFqn).subscribe(
(widgetTypeInfo) => {
const config: WidgetConfig = this.dashboardUtils.widgetConfigFromWidgetType(widgetTypeInfo);
if (!config.title) {
config.title = 'New ' + widgetTypeInfo.widgetName;
}
let newWidget: Widget = {
isSystemType: widget.isSystemType,
bundleAlias: widget.bundleAlias,
typeAlias: widgetTypeInfo.alias,
typeFullFqn: widgetTypeInfo.fullFqn,
type: widgetTypeInfo.type,
title: 'New widget',
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 { NULL_UUID } from '@shared/models/id/has-uuid';
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 { BehaviorSubject, combineLatest, Observable, of } from 'rxjs';
import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
@ -116,9 +116,7 @@ export class DashboardWidgetSelectComponent implements OnInit {
const widgetInfos = widgets.map((widgetTypeInfo) => {
widgetTypes.add(widgetTypeInfo.widgetType);
const widget: WidgetInfo = {
isSystemType: isSystem,
bundleAlias,
typeAlias: widgetTypeInfo.alias,
typeFullFqn: fullWidgetTypeFqn(widgetTypeInfo),
type: widgetTypeInfo.widgetType,
title: widgetTypeInfo.name,
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;
}
const widget = widgetItem.widget;
if (isUndefined(widget.isSystemType) ||
isUndefined(widget.bundleAlias) ||
isUndefined(widget.typeAlias) ||
if ((isUndefined(widget.typeFullFqn) && (isUndefined((widget as any).isSystemType) ||
isUndefined((widget as any).bundleAlias) ||
isUndefined((widget as any).typeAlias))) ||
isUndefined(widget.type)) {
return false;
}
@ -982,6 +982,7 @@ export class ImportExportService {
}
private prepareDashboardExport(dashboard: Dashboard): Dashboard {
dashboard = this.dashboardUtils.validateAndUpdateDashboard(dashboard);
dashboard = this.prepareExport(dashboard);
delete dashboard.assignedCustomers;
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(
{
widgetName: this.utils.editWidgetInfo.widgetName,
alias: 'customWidget',
bundleAlias: 'customWidgetBundle',
fullFqn: 'system.customWidget',
deprecated: false,
type: this.utils.editWidgetInfo.type,
sizeX: this.utils.editWidgetInfo.sizeX,
sizeY: this.utils.editWidgetInfo.sizeY,
@ -212,7 +214,7 @@ export class WidgetComponentService {
}
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) {
return widgetInfo;
} 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(
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.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 widgetInfo = this.widgetService.getWidgetInfoFromCache(bundleAlias, widgetTypeAlias, isSystem);
const widgetInfo = this.widgetService.getWidgetInfoFromCache(fullFqn);
if (widgetInfo) {
widgetInfoSubject.next(widgetInfo);
widgetInfoSubject.complete();
} else {
if (this.utils.widgetEditMode) {
this.loadWidget(this.editingWidgetType, bundleAlias, isSystem, widgetInfoSubject);
this.loadWidget(this.editingWidgetType, widgetInfoSubject);
} else {
const key = this.widgetService.createWidgetInfoCacheKey(bundleAlias, widgetTypeAlias, isSystem);
let fetchQueue = this.widgetsInfoFetchQueue.get(key);
let fetchQueue = this.widgetsInfoFetchQueue.get(fullFqn);
if (fetchQueue) {
fetchQueue.push(widgetInfoSubject);
} else {
fetchQueue = new Array<Subject<WidgetInfo>>();
this.widgetsInfoFetchQueue.set(key, fetchQueue);
this.widgetService.getWidgetType(bundleAlias, widgetTypeAlias, isSystem, {ignoreErrors: true}).subscribe(
this.widgetsInfoFetchQueue.set(fullFqn, fetchQueue);
this.widgetService.getWidgetType(fullFqn, {ignoreErrors: true}).subscribe(
(widgetType) => {
this.loadWidget(widgetType, bundleAlias, isSystem, widgetInfoSubject);
this.loadWidget(widgetType, widgetInfoSubject);
},
() => {
widgetInfoSubject.next(this.missingWidgetType);
widgetInfoSubject.complete();
this.resolveWidgetsInfoFetchQueue(key, this.missingWidgetType);
this.resolveWidgetsInfoFetchQueue(fullFqn, this.missingWidgetType);
}
);
}
@ -264,19 +265,18 @@ export class WidgetComponentService {
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 key = this.widgetService.createWidgetInfoCacheKey(bundleAlias, widgetInfo.alias, isSystem);
let widgetControllerDescriptor: WidgetControllerDescriptor = null;
try {
widgetControllerDescriptor = this.createWidgetControllerDescriptor(widgetInfo, key);
widgetControllerDescriptor = this.createWidgetControllerDescriptor(widgetInfo);
} catch (e) {
const details = this.utils.parseException(e);
const errorMessage = `Failed to compile widget script. \n Error: ${details.message}`;
this.processWidgetLoadError([errorMessage], key, widgetInfoSubject);
this.processWidgetLoadError([errorMessage], widgetInfo.fullFqn, widgetInfoSubject);
}
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(
() => {
if (widgetControllerDescriptor.settingsSchema) {
@ -291,15 +291,15 @@ export class WidgetComponentService {
widgetInfo.typeParameters = widgetControllerDescriptor.typeParameters;
widgetInfo.actionSources = widgetControllerDescriptor.actionSources;
widgetInfo.widgetTypeFunction = widgetControllerDescriptor.widgetTypeFunction;
this.widgetService.putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
this.widgetService.putWidgetInfoToCache(widgetInfo);
if (widgetInfoSubject) {
widgetInfoSubject.next(widgetInfo);
widgetInfoSubject.complete();
}
this.resolveWidgetsInfoFetchQueue(key, widgetInfo);
this.resolveWidgetsInfoFetchQueue(widgetInfo.fullFqn, widgetInfo);
},
(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 {
let widgetTypeFunctionBody = `return function _${name} (ctx) {\n` +
private createWidgetControllerDescriptor(widgetInfo: WidgetInfo): WidgetControllerDescriptor {
let widgetTypeFunctionBody = `return function _${widgetInfo.fullFqn.replace(/\./g, '_')} (ctx) {\n` +
' var self = this;\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) {
widgetInfoSubject.error({
widgetInfo: this.errorWidgetType,
errorMessages
});
}
this.resolveWidgetsInfoFetchQueue(cacheKey, this.errorWidgetType, errorMessages);
this.resolveWidgetsInfoFetchQueue(fullFqn, this.errorWidgetType, errorMessages);
}
private resolveWidgetsInfoFetchQueue(key: string, widgetInfo: WidgetInfo, errorMessages?: string[]) {
const fetchQueue = this.widgetsInfoFetchQueue.get(key);
private resolveWidgetsInfoFetchQueue(fullFqn: string, widgetInfo: WidgetInfo, errorMessages?: string[]) {
const fetchQueue = this.widgetsInfoFetchQueue.get(fullFqn);
if (fetchQueue) {
fetchQueue.forEach(subject => {
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,
forwardRef,
Input,
OnChanges,
OnDestroy,
OnInit,
SimpleChanges,
ViewChild,
ViewContainerRef
} 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;
@ -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 {
this.destroyBasicModeComponent();
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.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) => {
this.widgetInfo = widgetInfo;
this.loadFromWidgetInfo();
@ -404,7 +404,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
private loadFromWidgetInfo() {
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;
elem.classList.add('tb-widget');
elem.classList.add(this.widgetContext.widgetNamespace);
@ -761,8 +761,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
}
this.widgetContentContainer.clear();
this.handleWidgetException(e);
this.widgetComponentService.clearWidgetInfo(this.widgetInfo, this.widget.bundleAlias, this.widget.typeAlias,
this.widget.isSystemType);
this.widgetComponentService.clearWidgetInfo(this.widgetInfo);
throw e;
}

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

@ -20,6 +20,7 @@ import {
Datasource,
DatasourceData,
FormattedData,
fullWidgetTypeFqn,
JsonSettingsSchema,
Widget,
WidgetActionDescriptor,
@ -30,6 +31,7 @@ import {
widgetType,
WidgetTypeDescriptor,
WidgetTypeDetails,
widgetTypeFqn,
WidgetTypeParameters
} from '@shared/models/widget.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 { EdgeService } from '@core/http/edge.service';
import * as RxJS from 'rxjs';
import { BehaviorSubject, Observable } from 'rxjs';
import * as RxJSOperators from 'rxjs/operators';
import { TbPopoverComponent } from '@shared/components/popover.component';
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 { UserSettingsService } from '@core/http/user-settings.service';
import { DynamicComponentModule } from '@core/services/dynamic-component-factory.service';
import { BehaviorSubject, Observable } from 'rxjs';
export interface IWidgetAction {
name: string;
@ -446,11 +448,13 @@ export class WidgetContext {
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);
}
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);
}
}
@ -505,7 +509,9 @@ export interface IDynamicWidgetComponent {
export interface WidgetInfo extends WidgetTypeDescriptor, WidgetControllerDescriptor {
widgetName: string;
alias: string;
bundleAlias: string;
fullFqn: string;
deprecated: boolean;
typeSettingsSchema?: string | any;
typeDataKeySettingsSchema?: string | any;
typeLatestDataKeySettingsSchema?: string | any;
@ -536,7 +542,9 @@ export interface WidgetConfigComponentData {
export const MissingWidgetType: WidgetInfo = {
type: widgetType.latest,
widgetName: 'Widget type not found',
alias: 'undefined',
bundleAlias: 'undefined',
fullFqn: 'undefined',
deprecated: false,
sizeX: 8,
sizeY: 6,
resources: [],
@ -560,7 +568,9 @@ export const MissingWidgetType: WidgetInfo = {
export const ErrorWidgetType: WidgetInfo = {
type: widgetType.latest,
widgetName: 'Error loading widget',
alias: 'error',
bundleAlias: 'error',
fullFqn: 'error',
deprecated: false,
sizeX: 8,
sizeY: 6,
resources: [],
@ -599,48 +609,38 @@ export interface WidgetTypeInstance {
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);
widgetInfo.image = widgetTypeDetailsEntity.image;
widgetInfo.description = widgetTypeDetailsEntity.description;
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,
bundleAlias: string, createdTime: number): WidgetType {
export const toWidgetType = (widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId: TenantId,
bundleAlias: string, createdTime: number): WidgetType => {
const descriptor: WidgetTypeDescriptor = {
type: widgetInfo.type,
sizeX: widgetInfo.sizeX,
@ -664,14 +664,25 @@ export function toWidgetType(widgetInfo: WidgetInfo, id: WidgetTypeId, tenantId:
tenantId,
createdTime,
bundleAlias,
alias: widgetInfo.alias,
fqn: widgetTypeFqn(widgetInfo.fullFqn),
name: widgetInfo.widgetName,
deprecated: widgetInfo.deprecated,
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,
entityName?: string, entityLabel?: string) {
export const updateEntityParams = (params: StateParams, targetEntityParamName?: string, targetEntityId?: EntityId,
entityName?: string, entityLabel?: string) => {
if (targetEntityId) {
let targetEntityParams: StateParams;
if (targetEntityParamName && targetEntityParamName.length) {
@ -692,4 +703,4 @@ export function updateEntityParams(params: StateParams, targetEntityParamName?:
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) => {
if (saveWidgetAsData) {
this.widget.widgetName = saveWidgetAsData.widgetName;
this.widget.alias = undefined;
this.widget.fullFqn = undefined;
const config = JSON.parse(this.widget.defaultConfig);
config.title = this.widget.widgetName;
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;
return this.widgetsService.loadBundleLibraryWidgets(bundleAlias,
isSystem).pipe(
map((widgets) => {
return { widgets };
}
map((widgets) => ({ widgets })
));
}
}
@ -81,12 +79,10 @@ export class WidgetEditorDataResolver implements Resolve<WidgetEditorData> {
resolve(route: ActivatedRouteSnapshot): Observable<WidgetEditorData> {
const widgetTypeId = route.params.widgetTypeId;
return this.widgetsService.getWidgetTypeById(widgetTypeId).pipe(
map((result) => {
return {
map((result) => ({
widgetTypeDetails: 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,
this.entityService,
this.translate,
() => { return {
getStateParams(): StateParams {
return {};
}
} as IStateController;
},
() => ({
getStateParams: (): StateParams => ({})
} as IStateController),
{},
{});
@ -189,7 +186,7 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit {
).pipe(
mergeMap((result) => {
if (result) {
return this.widgetService.deleteWidgetType(widget.bundleAlias, widget.typeAlias, widget.isSystemType);
return this.widgetService.deleteWidgetType(widget.typeFullFqn);
} else {
return of(false);
}

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

@ -74,10 +74,10 @@ export interface DashboardLayoutInfo {
}
export interface LayoutDimension {
type?: LayoutType,
fixedWidth?: number,
fixedLayout?: DashboardLayoutId,
leftWidthPercentage?: number
type?: LayoutType;
fixedWidth?: number;
fixedLayout?: DashboardLayoutId;
leftWidthPercentage?: number;
}
export declare type DashboardLayoutId = 'main' | 'right';
@ -137,16 +137,16 @@ export interface HomeDashboardInfo {
hideDashboardToolbar: boolean;
}
export function isPublicDashboard(dashboard: DashboardInfo): boolean {
export const isPublicDashboard = (dashboard: DashboardInfo): boolean => {
if (dashboard && dashboard.assignedCustomers) {
return dashboard.assignedCustomers
.filter(customerInfo => customerInfo.public).length > 0;
} else {
return false;
}
}
};
export function getDashboardAssignedCustomersText(dashboard: DashboardInfo): string {
export const getDashboardAssignedCustomersText = (dashboard: DashboardInfo): string => {
if (dashboard && dashboard.assignedCustomers && dashboard.assignedCustomers.length > 0) {
return dashboard.assignedCustomers
.filter(customerInfo => !customerInfo.public)
@ -155,14 +155,13 @@ export function getDashboardAssignedCustomersText(dashboard: DashboardInfo): str
} else {
return '';
}
}
};
export function isCurrentPublicDashboardCustomer(dashboard: DashboardInfo, customerId: string): boolean {
export const isCurrentPublicDashboardCustomer = (dashboard: DashboardInfo, customerId: string): boolean => {
if (customerId && dashboard && dashboard.assignedCustomers) {
return dashboard.assignedCustomers.filter(customerInfo => {
return customerInfo.public && customerId === customerInfo.customerId.id;
}).length > 0;
return dashboard.assignedCustomers.filter(customerInfo =>
customerInfo.public && customerId === customerInfo.customerId.id).length > 0;
} else {
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 { Dashboard } from '@shared/models/dashboard.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 { ComponentStyle, Font, TimewindowStyle } from '@shared/models/widget-settings.models';
import { NULL_UUID } from '@shared/models/id/has-uuid';
export enum widgetType {
timeseries = 'timeseries',
@ -51,8 +52,7 @@ export enum widgetType {
}
export interface WidgetTypeTemplate {
bundleAlias: string;
alias: string;
fullFqn: string;
}
export interface WidgetTypeData {
@ -71,8 +71,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>(
icon: 'timeline',
configHelpLinkId: 'widgetsConfigTimeseries',
template: {
bundleAlias: 'charts',
alias: 'basic_timeseries'
fullFqn: 'system.charts.basic_timeseries'
}
}
],
@ -83,8 +82,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>(
icon: 'track_changes',
configHelpLinkId: 'widgetsConfigLatest',
template: {
bundleAlias: 'cards',
alias: 'attributes_card'
fullFqn: 'system.cards.attributes_card'
}
}
],
@ -95,8 +93,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>(
icon: 'mdi:developer-board',
configHelpLinkId: 'widgetsConfigRpc',
template: {
bundleAlias: 'gpio_widgets',
alias: 'basic_gpio_control'
fullFqn: 'system.gpio_widgets.basic_gpio_control'
}
}
],
@ -107,8 +104,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>(
icon: 'error',
configHelpLinkId: 'widgetsConfigAlarm',
template: {
bundleAlias: 'alarm_widgets',
alias: 'alarms_table'
fullFqn: 'system.alarm_widgets.alarms_table'
}
}
],
@ -119,8 +115,7 @@ export const widgetTypesData = new Map<widgetType, WidgetTypeData>(
icon: 'font_download',
configHelpLinkId: 'widgetsConfigStatic',
template: {
bundleAlias: 'cards',
alias: 'html_card'
fullFqn: 'system.cards.html_card'
}
}
]
@ -198,10 +193,38 @@ export interface WidgetControllerDescriptor {
export interface BaseWidgetType extends BaseData<WidgetTypeId> {
tenantId: TenantId;
bundleAlias: string;
alias: string;
fqn: 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 {
descriptor: WidgetTypeDescriptor;
}
@ -670,9 +693,7 @@ export interface Widget extends WidgetInfo{
export interface WidgetInfo {
id?: string;
isSystemType: boolean;
bundleAlias: string;
typeAlias: string;
typeFullFqn: string;
type: widgetType;
title: string;
image?: string;

Loading…
Cancel
Save