Browse Source

Merge remote-tracking branch 'upstream/master' into edge/misc-fixes

pull/7792/head
Volodymyr Babak 4 years ago
parent
commit
f399a80dee
  1. 9
      application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java
  2. 9
      application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java
  3. 116
      application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java
  4. 2
      pom.xml
  5. 3
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.html
  6. 15
      ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts
  7. 27
      ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts
  8. 24
      ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts
  9. 3
      ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html
  10. 8
      ui-ngx/src/app/modules/home/pages/widget/widget-library.component.scss
  11. 1
      ui-ngx/src/app/modules/home/pages/widget/widget-library.component.ts
  12. 23
      ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts
  13. 3
      ui-ngx/src/styles.scss

9
application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java

@ -40,6 +40,7 @@ import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfilePr
import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration;
import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
@ -104,7 +105,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
protected Device saveEntity(SecurityUser user, Device entity, Map<BulkImportColumnType, String> fields) {
DeviceCredentials deviceCredentials;
try {
deviceCredentials = createDeviceCredentials(fields);
deviceCredentials = createDeviceCredentials(entity.getTenantId(), entity.getId(), fields);
deviceCredentialsService.formatCredentials(deviceCredentials);
} catch (Exception e) {
throw new DeviceCredentialsValidationException("Invalid device credentials: " + e.getMessage());
@ -136,7 +137,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
}
@SneakyThrows
private DeviceCredentials createDeviceCredentials(Map<BulkImportColumnType, String> fields) {
private DeviceCredentials createDeviceCredentials(TenantId tenantId, DeviceId deviceId, Map<BulkImportColumnType, String> fields) {
DeviceCredentials credentials = new DeviceCredentials();
if (fields.containsKey(BulkImportColumnType.LWM2M_CLIENT_ENDPOINT)) {
credentials.setCredentialsType(DeviceCredentialsType.LWM2M_CREDENTIALS);
@ -147,7 +148,9 @@ public class DeviceBulkImportService extends AbstractBulkImportService<Device> {
} else if (CollectionUtils.containsAny(fields.keySet(), EnumSet.of(BulkImportColumnType.MQTT_CLIENT_ID, BulkImportColumnType.MQTT_USER_NAME, BulkImportColumnType.MQTT_PASSWORD))) {
credentials.setCredentialsType(DeviceCredentialsType.MQTT_BASIC);
setUpBasicMqttCredentials(fields, credentials);
} else {
} else if (deviceId != null && !fields.containsKey(BulkImportColumnType.ACCESS_TOKEN)) {
credentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(tenantId, deviceId);
} else {
credentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
setUpAccessTokenCredentials(fields, credentials);
}

9
application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java

@ -76,17 +76,18 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T
@Override
public Device saveDeviceWithCredentials(Device device, DeviceCredentials credentials, User user) throws ThingsboardException {
ActionType actionType = device.getId() == null ? ActionType.ADDED : ActionType.UPDATED;
boolean isCreate = device.getId() == null;
ActionType actionType = isCreate ? ActionType.ADDED : ActionType.UPDATED;
TenantId tenantId = device.getTenantId();
try {
Device oldDevice = isCreate ? null : deviceService.findDeviceById(tenantId, device.getId());
Device savedDevice = checkNotNull(deviceService.saveDeviceWithCredentials(device, credentials));
notificationEntityService.notifyCreateOrUpdateDevice(tenantId, savedDevice.getId(), savedDevice.getCustomerId(),
savedDevice, device, actionType, user);
savedDevice, oldDevice, actionType, user);
return savedDevice;
} catch (Exception e) {
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), device,
actionType, user, e);
notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.DEVICE), device, actionType, user, e);
throw e;
}
}

116
application/src/test/java/org/thingsboard/server/controller/BaseDeviceControllerTest.java

@ -38,6 +38,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile;
import org.thingsboard.server.common.data.EntitySubtype;
import org.thingsboard.server.common.data.OtaPackageInfo;
import org.thingsboard.server.common.data.SaveDeviceWithCredentialsRequest;
import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.data.Tenant;
@ -55,6 +56,9 @@ import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportRequest;
import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportResult;
import org.thingsboard.server.dao.device.DeviceDao;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException;
@ -180,6 +184,58 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
Assert.assertEquals(foundDevice.getName(), savedDevice.getName());
}
@Test
public void testSaveDeviceWithCredentials() throws Exception {
String testToken = "TEST_TOKEN";
Device device = new Device();
device.setName("My device");
device.setType("default");
DeviceCredentials deviceCredentials = new DeviceCredentials();
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
deviceCredentials.setCredentialsId(testToken);
SaveDeviceWithCredentialsRequest saveRequest = new SaveDeviceWithCredentialsRequest(device, deviceCredentials);
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
Device savedDevice = readResponse(doPost("/api/device-with-credentials", saveRequest).andExpect(status().isOk()), Device.class);
Device oldDevice = new Device(savedDevice);
testNotifyEntityOneTimeMsgToEdgeServiceNever(savedDevice, savedDevice.getId(), savedDevice.getId(),
savedTenant.getId(), tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(),
ActionType.ADDED);
testNotificationUpdateGatewayNever();
Assert.assertNotNull(savedDevice);
Assert.assertNotNull(savedDevice.getId());
Assert.assertTrue(savedDevice.getCreatedTime() > 0);
Assert.assertEquals(savedTenant.getId(), savedDevice.getTenantId());
Assert.assertNotNull(savedDevice.getCustomerId());
Assert.assertEquals(NULL_UUID, savedDevice.getCustomerId().getId());
Assert.assertEquals(device.getName(), savedDevice.getName());
DeviceCredentials foundDeviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
Assert.assertNotNull(foundDeviceCredentials);
Assert.assertNotNull(foundDeviceCredentials.getId());
Assert.assertEquals(savedDevice.getId(), foundDeviceCredentials.getDeviceId());
Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, foundDeviceCredentials.getCredentialsType());
Assert.assertEquals(testToken, foundDeviceCredentials.getCredentialsId());
Mockito.reset(tbClusterService, auditLogService, gatewayNotificationsService);
savedDevice.setName("My new device");
doPost("/api/device", savedDevice, Device.class);
testNotifyEntityAllOneTime(savedDevice, savedDevice.getId(), savedDevice.getId(), savedTenant.getId(),
tenantAdmin.getCustomerId(), tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.UPDATED);
testNotificationUpdateGatewayOneTime(savedDevice, oldDevice);
}
@Test
public void saveDeviceWithViolationOfValidation() throws Exception {
Device device = new Device();
@ -1235,6 +1291,66 @@ public abstract class BaseDeviceControllerTest extends AbstractControllerTest {
testEntityDaoWithRelationsTransactionalException(deviceDao, savedTenant.getId(), deviceId, "/api/device/" + deviceId);
}
@Test
public void testBulkImportDeviceWithoutCredentials() throws Exception {
String deviceName = "some_device";
String deviceType = "some_type";
BulkImportRequest request = new BulkImportRequest();
request.setFile(String.format("NAME,TYPE\n%s,%s", deviceName, deviceType));
BulkImportRequest.Mapping mapping = new BulkImportRequest.Mapping();
BulkImportRequest.ColumnMapping name = new BulkImportRequest.ColumnMapping();
name.setType(BulkImportColumnType.NAME);
BulkImportRequest.ColumnMapping type = new BulkImportRequest.ColumnMapping();
type.setType(BulkImportColumnType.TYPE);
List<BulkImportRequest.ColumnMapping> columns = new ArrayList<>();
columns.add(name);
columns.add(type);
mapping.setColumns(columns);
mapping.setDelimiter(',');
mapping.setUpdate(true);
mapping.setHeader(true);
request.setMapping(mapping);
BulkImportResult<Device> deviceBulkImportResult = doPostWithTypedResponse("/api/device/bulk_import", request, new TypeReference<>() {});
Assert.assertEquals(1, deviceBulkImportResult.getCreated().get());
Assert.assertEquals(0, deviceBulkImportResult.getErrors().get());
Assert.assertEquals(0, deviceBulkImportResult.getUpdated().get());
Assert.assertTrue(deviceBulkImportResult.getErrorsList().isEmpty());
Device savedDevice = doGet("/api/tenant/devices?deviceName=" + deviceName, Device.class);
Assert.assertNotNull(savedDevice);
Assert.assertEquals(deviceName, savedDevice.getName());
Assert.assertEquals(deviceType, savedDevice.getType());
DeviceCredentials savedCredentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
Assert.assertNotNull(savedCredentials);
Assert.assertNotNull(savedCredentials.getId());
Assert.assertEquals(savedDevice.getId(), savedCredentials.getDeviceId());
Assert.assertEquals(DeviceCredentialsType.ACCESS_TOKEN, savedCredentials.getCredentialsType());
Assert.assertNotNull(savedCredentials.getCredentialsId());
Assert.assertEquals(20, savedCredentials.getCredentialsId().length());
deviceBulkImportResult = doPostWithTypedResponse("/api/device/bulk_import", request, new TypeReference<>() {});
Assert.assertEquals(0, deviceBulkImportResult.getCreated().get());
Assert.assertEquals(0, deviceBulkImportResult.getErrors().get());
Assert.assertEquals(1, deviceBulkImportResult.getUpdated().get());
Assert.assertTrue(deviceBulkImportResult.getErrorsList().isEmpty());
Device updatedDevice = doGet("/api/device/" + savedDevice.getId().getId(), Device.class);
Assert.assertEquals(savedDevice, updatedDevice);
DeviceCredentials updatedCredentials =
doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class);
Assert.assertEquals(savedCredentials, updatedCredentials);
}
private Device createDevice(String name) {
Device device = new Device();
device.setName(name);

2
pom.xml

@ -77,7 +77,7 @@
<zookeeper.version>3.5.5</zookeeper.version>
<protobuf.version>3.21.9</protobuf.version>
<grpc.version>1.42.1</grpc.version>
<tbel.version>1.0.3</tbel.version>
<tbel.version>1.0.4</tbel.version>
<lombok.version>1.18.18</lombok.version>
<paho.client.version>1.2.4</paho.client.version>
<netty.version>4.1.75.Final</netty.version>

3
ui-ngx/src/app/modules/home/components/entity/entities-table.component.html

@ -251,7 +251,8 @@
<mat-row [fxShow]="!dataSource.dataLoading"
[ngClass]="{'mat-row-select': selectionEnabled,
'mat-selected': dataSource.selection.isSelected(entity),
'tb-current-entity': dataSource.isCurrentEntity(entity)}"
'tb-current-entity': dataSource.isCurrentEntity(entity),
'tb-pointer': entitiesTableConfig.rowPointer}"
*matRowDef="let entity; columns: displayedColumns;" (click)="onRowClick($event, entity)"></mat-row>
</table>
<span [fxShow]="!(isLoading$ | async) && (dataSource.isEmpty() | async) && !dataSource.dataLoading"

15
ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts

@ -155,6 +155,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
entitiesDeleteEnabled = true;
detailsPanelEnabled = true;
hideDetailsTabsOnEdit = true;
rowPointer = false;
actionsColumnTitle = null;
entityTranslations: EntityTypeTranslation;
entityResources: EntityTypeResource<T>;
@ -220,6 +221,20 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
}
}
toggleEntityDetails($event: Event, entity: T) {
if (this.table) {
this.table.toggleEntityDetails($event, entity);
}
}
isDetailsOpen(): boolean {
if (this.table) {
return this.table.isDetailsOpen;
} else {
return false;
}
}
getActivatedRoute(): ActivatedRoute {
if (this.table) {
return this.table.route;

27
ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts

@ -94,6 +94,8 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
this.config.entityTranslations = entityTypeTranslations.get(EntityType.DASHBOARD);
this.config.entityResources = entityTypeResources.get(EntityType.DASHBOARD);
this.config.rowPointer = true;
this.config.deleteEntityTitle = dashboard =>
this.translate.instant('dashboard.delete-dashboard-title', {dashboardTitle: dashboard.title});
this.config.deleteEntityContent = () => this.translate.instant('dashboard.delete-dashboard-text');
@ -107,6 +109,15 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
this.config.onEntityAction = action => this.onDashboardAction(action);
this.config.detailsReadonly = () => (this.config.componentsData.dashboardScope === 'customer_user' ||
this.config.componentsData.dashboardScope === 'edge_customer_user');
this.config.handleRowClick = ($event, dashboard) => {
if (this.config.isDetailsOpen()) {
this.config.toggleEntityDetails($event, dashboard);
} else {
this.openDashboard($event, dashboard);
}
return true;
};
}
resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<DashboardInfo | Dashboard>> {
@ -197,14 +208,6 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
configureCellActions(dashboardScope: string): Array<CellActionDescriptor<DashboardInfo>> {
const actions: Array<CellActionDescriptor<DashboardInfo>> = [];
actions.push(
{
name: this.translate.instant('dashboard.open-dashboard'),
icon: 'dashboard',
isEnabled: () => true,
onAction: ($event, entity) => this.openDashboard($event, entity)
}
);
if (dashboardScope === 'tenant') {
actions.push(
{
@ -271,6 +274,14 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
}
);
}
actions.push(
{
name: this.translate.instant('dashboard.dashboard-details'),
icon: 'edit',
isEnabled: () => true,
onAction: ($event, entity) => this.config.toggleEntityDetails($event, entity)
}
);
return actions;
}

24
ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts

@ -74,6 +74,8 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
this.config.entityTranslations = entityTypeTranslations.get(EntityType.RULE_CHAIN);
this.config.entityResources = entityTypeResources.get(EntityType.RULE_CHAIN);
this.config.rowPointer = true;
this.config.deleteEntityTitle = ruleChain => this.translate.instant('rulechain.delete-rulechain-title',
{ruleChainName: ruleChain.name});
this.config.deleteEntityContent = () => this.translate.instant('rulechain.delete-rulechain-text');
@ -83,6 +85,14 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
this.config.saveEntity = ruleChain => this.saveRuleChain(ruleChain);
this.config.deleteEntity = id => this.ruleChainService.deleteRuleChain(id.id);
this.config.onEntityAction = action => this.onRuleChainAction(action);
this.config.handleRowClick = ($event, ruleChain) => {
if (this.config.isDetailsOpen()) {
this.config.toggleEntityDetails($event, ruleChain);
} else {
this.openRuleChain($event, ruleChain);
}
return true;
};
}
resolve(route: ActivatedRouteSnapshot): EntityTableConfig<RuleChain> {
@ -214,12 +224,6 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
configureCellActions(ruleChainScope: string): Array<CellActionDescriptor<RuleChain>> {
const actions: Array<CellActionDescriptor<RuleChain>> = [];
actions.push(
{
name: this.translate.instant('rulechain.open-rulechain'),
icon: 'settings_ethernet',
isEnabled: () => true,
onAction: ($event, entity) => this.openRuleChain($event, entity)
},
{
name: this.translate.instant('rulechain.export'),
icon: 'file_download',
@ -275,6 +279,14 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
}
);
}
actions.push(
{
name: this.translate.instant('rulechain.rulechain-details'),
icon: 'edit',
isEnabled: () => true,
onAction: ($event, entity) => this.config.toggleEntityDetails($event, entity)
}
);
return actions;
}

3
ui-ngx/src/app/modules/home/pages/widget/widget-library.component.html

@ -27,7 +27,8 @@
style="display: flex;"
class="mat-headline tb-absolute-fill">widgets-bundle.empty</span>
</section>
<tb-dashboard #dashboard
<tb-dashboard class="tb-widget-library"
#dashboard
[aliasController]="aliasController"
[widgets]="widgetsData.widgets"
[widgetLayouts]="widgetsData.widgetLayouts"

8
ui-ngx/src/app/modules/home/pages/widget/widget-library.component.scss

@ -21,3 +21,11 @@
border-width: 2px;
}
}
:host ::ng-deep {
.tb-widget-library {
.tb-widget-container {
cursor: pointer;
}
}
}

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

@ -78,6 +78,7 @@ export class WidgetLibraryComponent extends PageComponent implements OnInit {
dashboardCallbacks: DashboardCallbacks = {
onEditWidget: this.openWidgetType.bind(this),
onWidgetClicked: this.openWidgetType.bind(this),
onExportWidget: this.exportWidgetType.bind(this),
onRemoveWidget: this.removeWidgetType.bind(this)
};

23
ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts

@ -61,6 +61,8 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon
this.config.entityResources = entityTypeResources.get(EntityType.WIDGETS_BUNDLE);
this.config.defaultSortOrder = {property: 'title', direction: Direction.ASC};
this.config.rowPointer = true;
this.config.entityTitle = (widgetsBundle) => widgetsBundle ?
widgetsBundle.title : '';
@ -89,17 +91,17 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon
);
this.config.cellActionDescriptors.push(
{
name: this.translate.instant('widgets-bundle.open-widgets-bundle'),
icon: 'now_widgets',
isEnabled: () => true,
onAction: ($event, entity) => this.openWidgetsBundle($event, entity)
},
{
name: this.translate.instant('widgets-bundle.export'),
icon: 'file_download',
isEnabled: () => true,
onAction: ($event, entity) => this.exportWidgetsBundle($event, entity)
},
{
name: this.translate.instant('widgets-bundle.widgets-bundle-details'),
icon: 'edit',
isEnabled: () => true,
onAction: ($event, entity) => this.config.toggleEntityDetails($event, entity)
}
);
@ -114,6 +116,15 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon
this.config.saveEntity = widgetsBundle => this.widgetsService.saveWidgetsBundle(widgetsBundle);
this.config.deleteEntity = id => this.widgetsService.deleteWidgetsBundle(id.id);
this.config.onEntityAction = action => this.onWidgetsBundleAction(action);
this.config.handleRowClick = ($event, widgetsBundle) => {
if (this.config.isDetailsOpen()) {
this.config.toggleEntityDetails($event, widgetsBundle);
} else {
this.openWidgetsBundle($event, widgetsBundle);
}
return true;
};
}
resolve(): EntityTableConfig<WidgetsBundle> {

3
ui-ngx/src/styles.scss

@ -1011,6 +1011,9 @@ mat-label {
&.tb-current-entity {
background-color: #e9e9e9;
}
&.tb-pointer {
cursor: pointer;
}
}
.mat-row:not(.mat-row-select), .mat-header-row:not(.mat-row-select) {

Loading…
Cancel
Save