Browse Source

Switch to unique widget type FQN.

pull/9111/head
Igor Kulikov 3 years ago
parent
commit
c57cb2b754
  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. 14
      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/sync/ie/WidgetsBundleExportData.java
  24. 12
      common/data/src/main/java/org/thingsboard/server/common/data/widget/BaseWidgetType.java
  25. 2
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetType.java
  26. 4
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeDetails.java
  27. 6
      common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetTypeInfo.java
  28. 2
      common/edge-api/src/main/proto/edge.proto
  29. 5
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  30. 16
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractWidgetTypeEntity.java
  31. 24
      dao/src/main/java/org/thingsboard/server/dao/service/validator/WidgetTypeDataValidator.java
  32. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDao.java
  33. 7
      dao/src/main/java/org/thingsboard/server/dao/sql/widget/WidgetTypeRepository.java
  34. 7
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeDao.java
  35. 37
      dao/src/main/java/org/thingsboard/server/dao/widget/WidgetTypeServiceImpl.java
  36. 6
      dao/src/main/resources/sql/schema-entities.sql
  37. 10
      dao/src/test/java/org/thingsboard/server/dao/service/WidgetTypeServiceTest.java
  38. 6
      dao/src/test/java/org/thingsboard/server/dao/sql/widget/JpaWidgetTypeDaoTest.java
  39. 50
      ui-ngx/src/app/core/http/widget.service.ts
  40. 35
      ui-ngx/src/app/core/services/dashboard-utils.service.ts
  41. 4
      ui-ngx/src/app/modules/dashboard/dashboard-pages.routing.module.ts
  42. 8
      ui-ngx/src/app/modules/home/components/attribute/attribute-table.component.ts
  43. 6
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts
  44. 6
      ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-widget-select.component.ts
  45. 7
      ui-ngx/src/app/modules/home/components/import-export/import-export.service.ts
  46. 60
      ui-ngx/src/app/modules/home/components/widget/widget-component.service.ts
  47. 17
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  48. 7
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  49. 107
      ui-ngx/src/app/modules/home/models/widget-component.models.ts
  50. 2
      ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts
  51. 10
      ui-ngx/src/app/modules/home/pages/widget/widget-library-routing.module.ts
  52. 11
      ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts
  53. 25
      ui-ngx/src/app/shared/models/dashboard.models.ts
  54. 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

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

@ -127,3 +127,17 @@ UPDATE resource
ALTER TABLE notification_request ALTER COLUMN info SET DATA TYPE varchar(1000000);
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/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;

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

@ -482,13 +482,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());
}
}

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: '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: '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