Commit c36af7311ef46d64a0396525045ac88d17ff048e

Authored by Vladyslav
Committed by Igor Kulikov
1 parent 9f266cd2

Add bulk provision support label (#2096)

* Add to asset support label

* Add support import label

* Add support update entity type and label

* Add translate asset label
Showing 27 changed files with 127 additions and 29 deletions
... ... @@ -119,6 +119,11 @@ public class ThingsboardInstallService {
119 119 case "2.4.0":
120 120 log.info("Upgrading ThingsBoard from version 2.4.0 to 2.4.1 ...");
121 121
  122 + case "2.4.1":
  123 + log.info("Upgrading ThingsBoard from version 2.4.1 to 2.4.2 ...");
  124 +
  125 + databaseUpgradeService.upgradeDatabase("2.4.1");
  126 +
122 127 log.info("Updating system data...");
123 128
124 129 systemDataLoaderService.deleteSystemWidgetBundle("charts");
... ...
... ... @@ -267,6 +267,15 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
267 267 } catch (InvalidQueryException e) {}
268 268 log.info("Schema updated.");
269 269 break;
  270 + case "2.4.1":
  271 + log.info("Updating schema ...");
  272 + String updateAssetTableStmt = "alter table asset add label text";
  273 + try {
  274 + cluster.getSession().execute(updateAssetTableStmt);
  275 + Thread.sleep(2500);
  276 + } catch (InvalidQueryException e) {}
  277 + log.info("Schema updated.");
  278 + break;
270 279 default:
271 280 throw new RuntimeException("Unable to upgrade Cassandra database, unsupported fromVersion: " + fromVersion);
272 281 }
... ...
... ... @@ -176,6 +176,15 @@ public class SqlDatabaseUpgradeService implements DatabaseUpgradeService {
176 176 log.info("Schema updated.");
177 177 }
178 178 break;
  179 + case "2.4.1":
  180 + try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
  181 + log.info("Updating schema ...");
  182 + try {
  183 + conn.createStatement().execute("ALTER TABLE asset ADD COLUMN label varchar(255)"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
  184 + } catch (Exception e) {}
  185 + log.info("Schema updated.");
  186 + }
  187 + break;
179 188 default:
180 189 throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
181 190 }
... ...
... ... @@ -31,6 +31,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
31 31 private CustomerId customerId;
32 32 private String name;
33 33 private String type;
  34 + private String label;
34 35
35 36 public Asset() {
36 37 super();
... ... @@ -46,6 +47,7 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
46 47 this.customerId = asset.getCustomerId();
47 48 this.name = asset.getName();
48 49 this.type = asset.getType();
  50 + this.label = asset.getLabel();
49 51 }
50 52
51 53 public TenantId getTenantId() {
... ... @@ -81,6 +83,14 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
81 83 this.type = type;
82 84 }
83 85
  86 + public String getLabel() {
  87 + return label;
  88 + }
  89 +
  90 + public void setLabel(String label) {
  91 + this.label = label;
  92 + }
  93 +
84 94 @Override
85 95 public String getSearchText() {
86 96 return getName();
... ... @@ -97,6 +107,8 @@ public class Asset extends SearchTextBasedWithAdditionalInfo<AssetId> implements
97 107 builder.append(name);
98 108 builder.append(", type=");
99 109 builder.append(type);
  110 + builder.append(", label=");
  111 + builder.append(label);
100 112 builder.append(", additionalInfo=");
101 113 builder.append(getAdditionalInfo());
102 114 builder.append(", createdTime=");
... ...
... ... @@ -197,6 +197,7 @@ public class ModelConstants {
197 197 public static final String ASSET_CUSTOMER_ID_PROPERTY = CUSTOMER_ID_PROPERTY;
198 198 public static final String ASSET_NAME_PROPERTY = "name";
199 199 public static final String ASSET_TYPE_PROPERTY = "type";
  200 + public static final String ASSET_LABEL_PROPERTY = "label";
200 201 public static final String ASSET_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
201 202
202 203 public static final String ASSET_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_tenant_and_search_text";
... ...
... ... @@ -37,6 +37,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ASSET_CUSTOMER_ID_
37 37 import static org.thingsboard.server.dao.model.ModelConstants.ASSET_NAME_PROPERTY;
38 38 import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TENANT_ID_PROPERTY;
39 39 import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TYPE_PROPERTY;
  40 +import static org.thingsboard.server.dao.model.ModelConstants.ASSET_LABEL_PROPERTY;
40 41 import static org.thingsboard.server.dao.model.ModelConstants.ID_PROPERTY;
41 42 import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
42 43
... ... @@ -64,6 +65,9 @@ public final class AssetEntity implements SearchTextEntity<Asset> {
64 65 @Column(name = ASSET_NAME_PROPERTY)
65 66 private String name;
66 67
  68 + @Column(name = ASSET_LABEL_PROPERTY)
  69 + private String label;
  70 +
67 71 @Column(name = SEARCH_TEXT_PROPERTY)
68 72 private String searchText;
69 73
... ... @@ -86,6 +90,7 @@ public final class AssetEntity implements SearchTextEntity<Asset> {
86 90 }
87 91 this.name = asset.getName();
88 92 this.type = asset.getType();
  93 + this.label = asset.getLabel();
89 94 this.additionalInfo = asset.getAdditionalInfo();
90 95 }
91 96
... ... @@ -163,8 +168,9 @@ public final class AssetEntity implements SearchTextEntity<Asset> {
163 168 }
164 169 asset.setName(name);
165 170 asset.setType(type);
  171 + asset.setLabel(label);
166 172 asset.setAdditionalInfo(additionalInfo);
167 173 return asset;
168 174 }
169 175
170   -}
\ No newline at end of file
  176 +}
... ...
... ... @@ -40,6 +40,7 @@ import static org.thingsboard.server.dao.model.ModelConstants.ASSET_CUSTOMER_ID_
40 40 import static org.thingsboard.server.dao.model.ModelConstants.ASSET_NAME_PROPERTY;
41 41 import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TENANT_ID_PROPERTY;
42 42 import static org.thingsboard.server.dao.model.ModelConstants.ASSET_TYPE_PROPERTY;
  43 +import static org.thingsboard.server.dao.model.ModelConstants.ASSET_LABEL_PROPERTY;
43 44 import static org.thingsboard.server.dao.model.ModelConstants.SEARCH_TEXT_PROPERTY;
44 45
45 46 @Data
... ... @@ -61,6 +62,9 @@ public final class AssetEntity extends BaseSqlEntity<Asset> implements SearchTex
61 62 @Column(name = ASSET_TYPE_PROPERTY)
62 63 private String type;
63 64
  65 + @Column(name = ASSET_LABEL_PROPERTY)
  66 + private String label;
  67 +
64 68 @Column(name = SEARCH_TEXT_PROPERTY)
65 69 private String searchText;
66 70
... ... @@ -84,6 +88,7 @@ public final class AssetEntity extends BaseSqlEntity<Asset> implements SearchTex
84 88 }
85 89 this.name = asset.getName();
86 90 this.type = asset.getType();
  91 + this.label = asset.getLabel();
87 92 this.additionalInfo = asset.getAdditionalInfo();
88 93 }
89 94
... ... @@ -113,8 +118,9 @@ public final class AssetEntity extends BaseSqlEntity<Asset> implements SearchTex
113 118 }
114 119 asset.setName(name);
115 120 asset.setType(type);
  121 + asset.setLabel(label);
116 122 asset.setAdditionalInfo(additionalInfo);
117 123 return asset;
118 124 }
119 125
120   -}
\ No newline at end of file
  126 +}
... ...
... ... @@ -244,6 +244,7 @@ CREATE TABLE IF NOT EXISTS thingsboard.asset (
244 244 customer_id timeuuid,
245 245 name text,
246 246 type text,
  247 + label text,
247 248 search_text text,
248 249 additional_info text,
249 250 PRIMARY KEY (id, tenant_id, customer_id, type)
... ... @@ -711,4 +712,4 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.entity_view_by_tenant_and_ent
711 712 AND search_text IS NOT NULL
712 713 AND id IS NOT NULL
713 714 PRIMARY KEY (tenant_id, entity_id, customer_id, search_text, id, type)
714   - WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);
\ No newline at end of file
  715 + WITH CLUSTERING ORDER BY (entity_id DESC, customer_id DESC, search_text ASC, id DESC);
... ...
... ... @@ -42,6 +42,7 @@ CREATE TABLE IF NOT EXISTS asset (
42 42 additional_info varchar,
43 43 customer_id varchar(31),
44 44 name varchar(255),
  45 + label varchar(255),
45 46 search_text varchar(255),
46 47 tenant_id varchar(31),
47 48 type varchar(255)
... ...
... ... @@ -1130,19 +1130,12 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
1130 1130 let statisticalInfo = {};
1131 1131 let newEntity = {
1132 1132 name: entityParameters.name,
1133   - type: entityParameters.type
  1133 + type: entityParameters.type,
  1134 + label: entityParameters.label
1134 1135 };
1135   - let promise;
1136   - switch (entityType) {
1137   - case types.entityType.device:
1138   - promise = deviceService.saveDevice(newEntity, config);
1139   - break;
1140   - case types.entityType.asset:
1141   - promise = assetService.saveAsset(newEntity, true, config);
1142   - break;
1143   - }
  1136 + let saveEntityPromise = getEntitySavePromise(entityType, newEntity, config);
1144 1137
1145   - promise.then(function success(response) {
  1138 + saveEntityPromise.then(function success(response) {
1146 1139 saveEntityRelation(entityType, response.id, entityParameters, config).then(function success() {
1147 1140 statisticalInfo.create = {
1148 1141 entity: 1
... ... @@ -1166,7 +1159,15 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
1166 1159 break;
1167 1160 }
1168 1161 findIdEntity.then(function success(response) {
1169   - saveEntityRelation(entityType, response.id, entityParameters, config).then(function success() {
  1162 + let promises = [];
  1163 + if(response.label !== entityParameters.label || response.type !== entityParameters.type){
  1164 + response.label = entityParameters.label;
  1165 + response.type = entityParameters.type;
  1166 + promises.push(getEntitySavePromise(entityType, response, config));
  1167 + }
  1168 + promises.push(saveEntityRelation(entityType, response.id, entityParameters, config));
  1169 +
  1170 + $q.all(promises).then(function success() {
1170 1171 statisticalInfo.update = {
1171 1172 entity: 1
1172 1173 };
... ... @@ -1193,6 +1194,19 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
1193 1194 return deferred.promise;
1194 1195 }
1195 1196
  1197 + function getEntitySavePromise(entityType, newEntity, config) {
  1198 + let promise;
  1199 + switch (entityType) {
  1200 + case types.entityType.device:
  1201 + promise = deviceService.saveDevice(newEntity, config);
  1202 + break;
  1203 + case types.entityType.asset:
  1204 + promise = assetService.saveAsset(newEntity, true, config);
  1205 + break;
  1206 + }
  1207 + return promise;
  1208 + }
  1209 +
1196 1210 function getRelatedEntity(entityId, keys, typeTranslatePrefix) {
1197 1211 var deferred = $q.defer();
1198 1212 getEntityPromise(entityId.entityType, entityId.id, {ignoreLoading: true}).then(
... ...
... ... @@ -64,6 +64,10 @@
64 64 entity-type="types.entityType.asset">
65 65 </tb-entity-subtype-autocomplete>
66 66 <md-input-container class="md-block">
  67 + <label translate>asset.label</label>
  68 + <input name="label" ng-model="asset.label">
  69 + </md-input-container>
  70 + <md-input-container class="md-block">
67 71 <label translate>asset.description</label>
68 72 <textarea ng-model="asset.additionalInfo.description" rows="2"></textarea>
69 73 </md-input-container>
... ...
... ... @@ -369,6 +369,10 @@ export default angular.module('thingsboard.types', [])
369 369 name: 'import.column-type.type',
370 370 value: 'type'
371 371 },
  372 + label: {
  373 + name: 'import.column-type.label',
  374 + value: 'label'
  375 + },
372 376 clientAttribute: {
373 377 name: 'import.column-type.client-attribute',
374 378 value: 'CLIENT_ATTRIBUTE'
... ...
... ... @@ -98,7 +98,7 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo
98 98 vm.columnsParam = [];
99 99 var columnParam = {};
100 100 for (var i = 0; i < parseData.headers.length; i++) {
101   - if (vm.importParameters.isHeader && parseData.headers[i].search(/^(name|type)$/im) === 0) {
  101 + if (vm.importParameters.isHeader && parseData.headers[i].search(/^(name|type|label)$/im) === 0) {
102 102 columnParam = {
103 103 type: types.importEntityColumnType[parseData.headers[i].toLowerCase()].value,
104 104 key: parseData.headers[i].toLowerCase(),
... ... @@ -126,6 +126,7 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo
126 126 var entityData = {
127 127 name: "",
128 128 type: "",
  129 + label: "",
129 130 accessToken: "",
130 131 attributes: {
131 132 server: [],
... ... @@ -162,6 +163,9 @@ export default function ImportDialogCsvController($scope, $mdDialog, toast, impo
162 163 case types.importEntityColumnType.type.value:
163 164 entityData.type = importData.rows[i][j];
164 165 break;
  166 + case types.importEntityColumnType.label.value:
  167 + entityData.label = importData.rows[i][j];
  168 + break;
165 169 }
166 170 }
167 171 entitiesData.push(entityData);
... ...
... ... @@ -44,6 +44,7 @@ function TableColumnsAssignmentController($scope, types, $timeout) {
44 44
45 45 vm.columnTypes.name = types.importEntityColumnType.name;
46 46 vm.columnTypes.type = types.importEntityColumnType.type;
  47 + vm.columnTypes.label = types.importEntityColumnType.label;
47 48
48 49 switch (vm.entityType) {
49 50 case types.entityType.device:
... ... @@ -62,6 +63,7 @@ function TableColumnsAssignmentController($scope, types, $timeout) {
62 63 if (newVal) {
63 64 var isSelectName = false;
64 65 var isSelectType = false;
  66 + var isSelectLabel = false;
65 67 var isSelectCredentials = false;
66 68 for (var i = 0; i < newVal.length; i++) {
67 69 switch (newVal[i].type) {
... ... @@ -71,6 +73,9 @@ function TableColumnsAssignmentController($scope, types, $timeout) {
71 73 case types.importEntityColumnType.type.value:
72 74 isSelectType = true;
73 75 break;
  76 + case types.importEntityColumnType.label.value:
  77 + isSelectLabel = true;
  78 + break;
74 79 case types.importEntityColumnType.accessToken.value:
75 80 isSelectCredentials = true;
76 81 break;
... ... @@ -84,6 +89,7 @@ function TableColumnsAssignmentController($scope, types, $timeout) {
84 89 $timeout(function () {
85 90 vm.columnTypes.name.disable = isSelectName;
86 91 vm.columnTypes.type.disable = isSelectType;
  92 + vm.columnTypes.label.disable = isSelectLabel;
87 93 if (angular.isDefined(vm.columnTypes.accessToken)) {
88 94 vm.columnTypes.accessToken.disable = isSelectCredentials;
89 95 }
... ...
... ... @@ -41,6 +41,7 @@
41 41 <md-input-container md-no-float
42 42 ng-if="column.type != vm.columnTypes.name.value &&
43 43 column.type != vm.columnTypes.type.value &&
  44 + column.type != vm.columnTypes.label.value &&
44 45 column.type != vm.columnTypes.accessToken.value">
45 46 <input required name="columnKeyName"
46 47 placeholder="{{ 'import.column-value' | translate }}"
... ...
... ... @@ -246,7 +246,8 @@
246 246 "select-asset": "Vybrat aktivum",
247 247 "no-assets-matching": "Žádná aktiva odpovídající '{{entity}}' nebyla nalezena.",
248 248 "asset-required": "Aktivum je povinné",
249   - "name-starts-with": "Název aktiva začíná"
  249 + "name-starts-with": "Název aktiva začíná",
  250 + "label": "Název"
250 251 },
251 252 "attribute": {
252 253 "attributes": "Atributy",
... ...
... ... @@ -267,7 +267,8 @@
267 267 "select-asset": "Objekt auswählen",
268 268 "no-assets-matching": "Es wurden keine zu '{{entity}}' passenden Objekte gefunden.",
269 269 "asset-required": "Objekt ist erforderlich",
270   - "name-starts-with": "Name des Objekts beginnt mit"
  270 + "name-starts-with": "Name des Objekts beginnt mit",
  271 + "label": "Bezeichnung"
271 272 },
272 273 "attribute": {
273 274 "attributes": "Eigenschaften",
... ...
... ... @@ -271,7 +271,8 @@
271 271 "asset-required": "Asset is required",
272 272 "name-starts-with": "Asset name starts with",
273 273 "import": "Import assets",
274   - "asset-file": "Asset file"
  274 + "asset-file": "Asset file",
  275 + "label": "Label"
275 276 },
276 277 "attribute": {
277 278 "attributes": "Attributes",
... ... @@ -1139,6 +1140,7 @@
1139 1140 "column-type": {
1140 1141 "name": "Name",
1141 1142 "type": "Type",
  1143 + "label": "Label",
1142 1144 "column-type": "Column type",
1143 1145 "client-attribute": "Client attribute",
1144 1146 "shared-attribute": "Shared attribute",
... ...
... ... @@ -271,7 +271,8 @@
271 271 "asset-required": "El activo es requerido",
272 272 "name-starts-with": "El nombre del activo comienza con",
273 273 "import": "Importar activos",
274   - "asset-file": "Archivo del activo"
  274 + "asset-file": "Archivo del activo",
  275 + "label": "Etiqueta"
275 276 },
276 277 "attribute": {
277 278 "attributes": "Atributos",
... ...
... ... @@ -246,7 +246,8 @@
246 246 "select-asset": "انتخاب دارايي",
247 247 "no-assets-matching": ".يافت نشد '{{entity}}' هيچ دارايي منطبق بر",
248 248 "asset-required": "دارايي مود نياز است",
249   - "name-starts-with": "نام دارايي شروع مي شود با"
  249 + "name-starts-with": "نام دارايي شروع مي شود با",
  250 + "label": "برچسب"
250 251 },
251 252 "attribute": {
252 253 "attributes": "ويژگي ها",
... ...
... ... @@ -271,7 +271,8 @@
271 271 "unassign-assets-text": "Après la confirmation, tous les actifs sélectionnés ne seront pas attribués et ne seront pas accessibles au client.",
272 272 "unassign-assets-title": "Êtes-vous sûr de vouloir retirer l'attribution de {count, plural, 1 {1 asset} other {# assets}}?",
273 273 "unassign-from-customer": "Retirer du client",
274   - "view-assets": "Afficher les actifs"
  274 + "view-assets": "Afficher les actifs",
  275 + "label": "Label"
275 276 },
276 277 "attribute": {
277 278 "add": "Ajouter un attribut",
... ...
... ... @@ -268,7 +268,8 @@
268 268 "select-asset": "Seleziona asset",
269 269 "no-assets-matching": "Nessun asset corrispondente a '{{entity}}' è stato trovato.",
270 270 "asset-required": "Asset obbligatorio",
271   - "name-starts-with": "Asset con nome che inizia per"
  271 + "name-starts-with": "Asset con nome che inizia per",
  272 + "label": "Etichetta"
272 273 },
273 274 "attribute": {
274 275 "attributes": "Attributi",
... ...
... ... @@ -236,7 +236,8 @@
236 236 "select-asset": "アセットを選択",
237 237 "no-assets-matching": "'{{entity}}'発見されました。",
238 238 "asset-required": "資産が必要です",
239   - "name-starts-with": "アセット名はで始まります"
  239 + "name-starts-with": "アセット名はで始まります",
  240 + "label": "ラベル"
240 241 },
241 242 "attribute": {
242 243 "attributes": "属性",
... ...
... ... @@ -250,7 +250,8 @@
250 250 "asset-required": "Актив обязателен",
251 251 "name-starts-with": "Название актива, начинающееся с",
252 252 "import": "Импортировать активы",
253   - "asset-file": "Файл с активами"
  253 + "asset-file": "Файл с активами",
  254 + "label": "Метка"
254 255 },
255 256 "attribute": {
256 257 "attributes": "Атрибуты",
... ... @@ -1113,6 +1114,7 @@
1113 1114 "column-type": {
1114 1115 "name": "Название",
1115 1116 "type": "Тип",
  1117 + "label": "Метка",
1116 1118 "column-type": "Тип колонки",
1117 1119 "client-attribute": "Клиентский атрибут",
1118 1120 "shared-attribute": "Общий атрибут",
... ...
... ... @@ -236,7 +236,8 @@
236 236 "select-asset": "Varlık seç",
237 237 "no-assets-matching": "'{{entity}}' isimli varlık bulunamadı.",
238 238 "asset-required": "Varlık gerekli",
239   - "name-starts-with": "... ile başlayan varlık adı"
  239 + "name-starts-with": "... ile başlayan varlık adı",
  240 + "label": "Etiket"
240 241 },
241 242 "attribute": {
242 243 "attributes": "Öznitelikler",
... ...
... ... @@ -280,7 +280,8 @@
280 280 "list-of-groups": "{ count, plural, 1 {Одна група активів} other {Список # груп активів} }",
281 281 "group-name-starts-with": "Групи активів, чиї імена починаються з '{{prefix}}'",
282 282 "import": "Імпортувати активи",
283   - "asset-file": "Файл з активами"
  283 + "asset-file": "Файл з активами",
  284 + "label": "Мітка"
284 285 },
285 286 "attribute": {
286 287 "attributes": "Атрибути",
... ... @@ -1365,6 +1366,7 @@
1365 1366 "column-type": {
1366 1367 "name": "Назва",
1367 1368 "type": "Тип",
  1369 + "label": "Мітка",
1368 1370 "column-type": "Тип колонки",
1369 1371 "client-attribute": "Атрибут клієнта",
1370 1372 "shared-attribute": "Спільний атрибут",
... ... @@ -2343,4 +2345,4 @@
2343 2345 "cs_CZ": "Чеська"
2344 2346 }
2345 2347 }
2346   -}
  2348 +}
\ No newline at end of file
... ...
... ... @@ -241,7 +241,8 @@
241 241 "select-asset": "选择资产",
242 242 "no-assets-matching": "没有找到匹配 '{{entity}}' 的资产。",
243 243 "asset-required": "资产必填",
244   - "name-starts-with": "资产名称以此开头"
  244 + "name-starts-with": "资产名称以此开头",
  245 + "label": "标签"
245 246 },
246 247 "attribute": {
247 248 "attributes": "属性",
... ...