Browse Source

Merge branch 'master' into feature/new-menu

pull/15888/head
Igor Kulikov 2 weeks ago
parent
commit
83f9b013d7
  1. 24
      application/pom.xml
  2. 221
      application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java
  3. 2
      common/data/src/main/java/org/thingsboard/server/common/data/Dashboard.java
  4. 29
      ui-ngx/src/app/modules/home/components/widget/lib/cards/api-usage-widget.component.ts
  5. 20
      ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.html

24
application/pom.xml

@ -507,12 +507,31 @@
</properties>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<executions>
<!-- Reserve free ephemeral ports so concurrent openapi-spec builds (and orphaned
forks from prior failed builds) on the same CI agent don't collide on fixed ports. -->
<execution>
<id>reserve-openapi-ports</id>
<phase>initialize</phase>
<goals><goal>reserve-network-port</goal></goals>
<configuration>
<portNames>
<portName>jmx.port</portName>
<portName>http.port</portName>
</portNames>
</configuration>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>org.thingsboard.server.ThingsboardServerApplication</mainClass>
<jmxPort>9001</jmxPort>
<jmxPort>${jmx.port}</jmxPort>
</configuration>
<executions>
<execution>
@ -532,6 +551,7 @@
<arguments>
<argument>--spring.config.name=thingsboard</argument>
<argument>--spring.profiles.active=openapi</argument>
<argument>--server.port=${http.port}</argument>
</arguments>
<maxAttempts>180</maxAttempts>
<wait>2000</wait>
@ -557,7 +577,7 @@
</execution>
</executions>
<configuration>
<apiDocsUrl>http://localhost:8080/v3/api-docs/thingsboard</apiDocsUrl>
<apiDocsUrl>http://localhost:${http.port}/v3/api-docs/thingsboard</apiDocsUrl>
<outputFileName>openapi.json</outputFileName>
<outputDir>${project.build.directory}</outputDir>
</configuration>

221
application/src/test/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessorTest.java

@ -0,0 +1,221 @@
/**
* Copyright © 2016-2026 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.edge.rpc.processor;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.junit.jupiter.api.Test;
import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.server.common.data.Dashboard;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.User;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.device.data.DeviceData;
import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration;
import org.thingsboard.server.common.data.device.data.DefaultDeviceTransportConfiguration;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.DashboardId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.gen.transport.TransportProtos;
import java.util.UUID;
import static org.assertj.core.api.Assertions.assertThat;
public class BaseEdgeProcessorTest {
private final BaseEdgeProcessor processor = new BaseEdgeProcessor() {
@Override
public ListenableFuture<Void> processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto msg) {
return Futures.immediateFuture(null);
}
};
@Test
public void dashboardSaveRequiredWhenConfigurationChanges() {
DashboardId dashboardId = new DashboardId(UUID.randomUUID());
Dashboard current = new Dashboard(dashboardId);
current.setTitle("Dashboard");
current.setConfiguration(JacksonUtil.toJsonNode("{\"widgets\":{\"w1\":{\"type\":\"timeseries\"}}}"));
Dashboard updated = new Dashboard(current);
updated.setConfiguration(JacksonUtil.toJsonNode("{\"widgets\":{\"w1\":{\"type\":\"latest\"}}}"));
assertThat(processor.isSaveRequired(current, updated)).isTrue();
}
@Test
public void dashboardSaveNotRequiredWhenOnlyVersionChanges() {
DashboardId dashboardId = new DashboardId(UUID.randomUUID());
Dashboard current = new Dashboard(dashboardId);
current.setTitle("Dashboard");
current.setConfiguration(JacksonUtil.toJsonNode("{\"widgets\":{\"w1\":{\"type\":\"timeseries\"}}}"));
current.setVersion(1L);
Dashboard updated = new Dashboard(current);
updated.setVersion(2L);
assertThat(processor.isSaveRequired(current, updated)).isFalse();
}
@Test
public void dashboardSaveNotRequiredWhenIdentical() {
DashboardId dashboardId = new DashboardId(UUID.randomUUID());
Dashboard current = new Dashboard(dashboardId);
current.setTitle("Dashboard");
current.setConfiguration(JacksonUtil.toJsonNode("{\"widgets\":{\"w1\":{\"type\":\"timeseries\"}}}"));
Dashboard updated = new Dashboard(current);
assertThat(processor.isSaveRequired(current, updated)).isFalse();
}
@Test
public void deviceSaveRequiredWhenDeviceDataChanges() {
DeviceId deviceId = new DeviceId(UUID.randomUUID());
Device current = new Device(deviceId);
current.setName("Device");
current.setDeviceData(deviceDataWithTransport());
Device updated = new Device(current);
DeviceData withoutTransport = new DeviceData();
withoutTransport.setConfiguration(new DefaultDeviceConfiguration());
updated.setDeviceData(withoutTransport);
assertThat(processor.isSaveRequired(current, updated)).isTrue();
}
@Test
public void deviceSaveRequiredWhenAdditionalInfoChanges() {
DeviceId deviceId = new DeviceId(UUID.randomUUID());
Device current = new Device(deviceId);
current.setName("Device");
current.setAdditionalInfo(JacksonUtil.toJsonNode("{\"description\":\"old\"}"));
Device updated = new Device(current);
updated.setAdditionalInfo(JacksonUtil.toJsonNode("{\"description\":\"new\"}"));
assertThat(processor.isSaveRequired(current, updated)).isTrue();
}
@Test
public void deviceSaveNotRequiredWhenOnlyVersionChanges() {
DeviceId deviceId = new DeviceId(UUID.randomUUID());
Device current = new Device(deviceId);
current.setName("Device");
current.setDeviceData(deviceDataWithTransport());
current.setAdditionalInfo(JacksonUtil.toJsonNode("{\"description\":\"same\"}"));
current.setVersion(1L);
Device updated = new Device(current);
updated.setVersion(2L);
assertThat(processor.isSaveRequired(current, updated)).isFalse();
}
@Test
public void assetSaveRequiredWhenAdditionalInfoChanges() {
AssetId assetId = new AssetId(UUID.randomUUID());
Asset current = new Asset(assetId);
current.setName("Asset");
current.setAdditionalInfo(JacksonUtil.toJsonNode("{\"description\":\"old\"}"));
Asset updated = new Asset(current);
updated.setAdditionalInfo(JacksonUtil.toJsonNode("{\"description\":\"new\"}"));
assertThat(processor.isSaveRequired(current, updated)).isTrue();
}
@Test
public void assetSaveNotRequiredWhenOnlyVersionChanges() {
AssetId assetId = new AssetId(UUID.randomUUID());
Asset current = new Asset(assetId);
current.setName("Asset");
current.setAdditionalInfo(JacksonUtil.toJsonNode("{\"description\":\"same\"}"));
current.setVersion(1L);
Asset updated = new Asset(current);
updated.setVersion(2L);
assertThat(processor.isSaveRequired(current, updated)).isFalse();
}
@Test
public void entityViewSaveRequiredWhenAdditionalInfoChanges() {
EntityViewId entityViewId = new EntityViewId(UUID.randomUUID());
EntityView current = new EntityView(entityViewId);
current.setName("EntityView");
current.setAdditionalInfo(JacksonUtil.toJsonNode("{\"description\":\"old\"}"));
EntityView updated = new EntityView(current);
updated.setAdditionalInfo(JacksonUtil.toJsonNode("{\"description\":\"new\"}"));
assertThat(processor.isSaveRequired(current, updated)).isTrue();
}
@Test
public void entityViewSaveNotRequiredWhenOnlyVersionChanges() {
EntityViewId entityViewId = new EntityViewId(UUID.randomUUID());
EntityView current = new EntityView(entityViewId);
current.setName("EntityView");
current.setAdditionalInfo(JacksonUtil.toJsonNode("{\"description\":\"same\"}"));
current.setVersion(1L);
EntityView updated = new EntityView(current);
updated.setVersion(2L);
assertThat(processor.isSaveRequired(current, updated)).isFalse();
}
@Test
public void userSaveRequiredWhenAdditionalInfoChanges() {
UserId userId = new UserId(UUID.randomUUID());
User current = new User(userId);
current.setEmail("user@thingsboard.io");
current.setAdditionalInfo(JacksonUtil.toJsonNode("{\"description\":\"old\"}"));
User updated = new User(current);
updated.setAdditionalInfo(JacksonUtil.toJsonNode("{\"description\":\"new\"}"));
assertThat(processor.isSaveRequired(current, updated)).isTrue();
}
@Test
public void userSaveNotRequiredWhenOnlyVersionChanges() {
UserId userId = new UserId(UUID.randomUUID());
User current = new User(userId);
current.setEmail("user@thingsboard.io");
current.setAdditionalInfo(JacksonUtil.toJsonNode("{\"description\":\"same\"}"));
current.setVersion(1L);
User updated = new User(current);
updated.setVersion(2L);
assertThat(processor.isSaveRequired(current, updated)).isFalse();
}
private static DeviceData deviceDataWithTransport() {
DeviceData deviceData = new DeviceData();
deviceData.setConfiguration(new DefaultDeviceConfiguration());
deviceData.setTransportConfiguration(new DefaultDeviceTransportConfiguration());
return deviceData;
}
}

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

@ -38,7 +38,7 @@ public class Dashboard extends DashboardInfo implements ExportableEntity<Dashboa
private static final long serialVersionUID = 872682138346187503L;
private transient JsonNode configuration;
private JsonNode configuration;
@Getter
@Setter

29
ui-ngx/src/app/modules/home/components/widget/lib/cards/api-usage-widget.component.ts

@ -30,6 +30,7 @@ import {
ApiUsageWidgetSettings,
getUniqueDataKeys
} from '@home/components/widget/lib/settings/cards/api-usage-settings.component.models';
import { ShortNumberPipe } from '@shared/pipe/short-number.pipe';
@Component({
selector: 'tb-api-usage-widget',
@ -57,18 +58,12 @@ export class ApiUsageWidgetComponent implements OnInit, OnDestroy {
noDataDisplayMessageText: string;
private contentResize$: ResizeObserver;
private powers: {key: string, value: number}[] = [
{ key: 'Q', value: 1e15 },
{ key: 'T', value: 1e12 },
{ key: 'B', value: 1e9 },
{ key: 'M', value: 1e6 },
{ key: 'K', value: 1e3 }
];
constructor(private imagePipe: ImagePipe,
private utils: UtilsService,
private sanitizer: DomSanitizer,
private cd: ChangeDetectorRef) {
private cd: ChangeDetectorRef,
private shortNumberPipe: ShortNumberPipe) {
}
ngOnInit(): void {
@ -95,8 +90,8 @@ export class ApiUsageWidgetComponent implements OnInit, OnDestroy {
const progress = (this.isFiniteNumber(data[0][key.maxLimit.key]) && data[0][key.maxLimit.key] !== 0) ? Math.min(100, ((data[0][key.current.key] / data[0][key.maxLimit.key]) * 100)) : 0;
key.progress = isFinite(progress) ? progress : 0;
key.status.value = data[0][key.status.key] ? data[0][key.status.key].toLowerCase() : 'enabled';
key.maxLimit.value = this.isFiniteNumber(data[0][key.maxLimit.key]) && data[0][key.maxLimit.key] !== 0 ? this.toShortNumber(data[0][key.maxLimit.key]) : '∞';
key.current.value = this.isFiniteNumber(data[0][key.current.key]) ? this.toShortNumber(data[0][key.current.key]) : 0;
key.maxLimit.value = this.isFiniteNumber(data[0][key.maxLimit.key]) && data[0][key.maxLimit.key] !== 0 ? this.shortNumberPipe.transform(data[0][key.maxLimit.key]) : '∞';
key.current.value = this.isFiniteNumber(data[0][key.current.key]) ? this.shortNumberPipe.transform(data[0][key.current.key], {roundDown: true}) : 0;
});
this.cd.detectChanges();
}
@ -149,20 +144,6 @@ export class ApiUsageWidgetComponent implements OnInit, OnDestroy {
}
}
private toShortNumber(number: any, decimals = 1) {
if (!Number.isFinite(number) || number < 0) {
return '0';
}
for (const power of this.powers) {
if (number >= power.value) {
const reduced = number / power.value;
const rounded = Number(reduced.toFixed(decimals));
return `${rounded}${power.key}`;
}
}
return `${Number(number.toFixed(decimals))}`;
}
public onInit() {
const borderRadius = this.ctx.$widgetElement.css('borderRadius');
this.overlayStyle = {...this.overlayStyle, ...{borderRadius}};

20
ui-ngx/src/app/modules/home/components/widget/lib/home-page/usage-info-widget.component.html

@ -36,11 +36,11 @@
</div>
<div class="tb-usage-items-values">
<div class="tb-usage-items-counts">
<div class="tb-usage-item-counts" [class.critical]="entityItemCritical.devices">{{ usageInfo?.devices | shortNumber }} / {{ maxValue(usageInfo?.maxDevices) }}</div>
<div class="tb-usage-item-counts" [class.critical]="entityItemCritical.assets">{{ usageInfo?.assets | shortNumber }} / {{ maxValue(usageInfo?.maxAssets) }}</div>
<div class="tb-usage-item-counts" [class.critical]="entityItemCritical.users">{{ usageInfo?.users | shortNumber }} / {{ maxValue(usageInfo?.maxUsers) }}</div>
<div class="tb-usage-item-counts" [class.critical]="entityItemCritical.dashboards">{{ usageInfo?.dashboards | shortNumber }} / {{ maxValue(usageInfo?.maxDashboards) }}</div>
<div class="tb-usage-item-counts" [class.critical]="entityItemCritical.customers">{{ usageInfo?.customers | shortNumber }} / {{ maxValue(usageInfo?.maxCustomers) }}</div>
<div class="tb-usage-item-counts" [class.critical]="entityItemCritical.devices">{{ usageInfo?.devices | shortNumber: {roundDown: true} }} / {{ maxValue(usageInfo?.maxDevices) }}</div>
<div class="tb-usage-item-counts" [class.critical]="entityItemCritical.assets">{{ usageInfo?.assets | shortNumber: {roundDown: true} }} / {{ maxValue(usageInfo?.maxAssets) }}</div>
<div class="tb-usage-item-counts" [class.critical]="entityItemCritical.users">{{ usageInfo?.users | shortNumber: {roundDown: true} }} / {{ maxValue(usageInfo?.maxUsers) }}</div>
<div class="tb-usage-item-counts" [class.critical]="entityItemCritical.dashboards">{{ usageInfo?.dashboards | shortNumber: {roundDown: true} }} / {{ maxValue(usageInfo?.maxDashboards) }}</div>
<div class="tb-usage-item-counts" [class.critical]="entityItemCritical.customers">{{ usageInfo?.customers | shortNumber: {roundDown: true} }} / {{ maxValue(usageInfo?.maxCustomers) }}</div>
</div>
<div class="tb-usage-items-progress">
<mat-progress-bar [class.critical]="entityItemCritical.devices" color="primary" mode="determinate" [value]="progressValue(usageInfo?.devices, usageInfo?.maxDevices)"></mat-progress-bar>
@ -63,11 +63,11 @@
</div>
<div class="tb-usage-items-values">
<div class="tb-usage-items-counts">
<div class="tb-usage-item-counts" [class.critical]="apiCallItemCritical.transportMessages">{{ usageInfo?.transportMessages | shortNumber }} / {{ maxValue(usageInfo?.maxTransportMessages) }}</div>
<div class="tb-usage-item-counts" [class.critical]="apiCallItemCritical.jsExecutions">{{ usageInfo?.jsExecutions | shortNumber }} / {{ maxValue(usageInfo?.maxJsExecutions) }}</div>
<div class="tb-usage-item-counts" [class.critical]="apiCallItemCritical.alarms">{{ usageInfo?.alarms | shortNumber }} / {{ maxValue(usageInfo?.maxAlarms) }}</div>
<div class="tb-usage-item-counts" [class.critical]="apiCallItemCritical.emails">{{ usageInfo?.emails | shortNumber }} / {{ maxValue(usageInfo?.maxEmails) }}</div>
<div class="tb-usage-item-counts" [class.critical]="apiCallItemCritical.sms">{{ usageInfo?.sms | shortNumber }} / {{ maxValue(usageInfo?.maxSms) }}</div>
<div class="tb-usage-item-counts" [class.critical]="apiCallItemCritical.transportMessages">{{ usageInfo?.transportMessages | shortNumber: {roundDown: true} }} / {{ maxValue(usageInfo?.maxTransportMessages) }}</div>
<div class="tb-usage-item-counts" [class.critical]="apiCallItemCritical.jsExecutions">{{ usageInfo?.jsExecutions | shortNumber: {roundDown: true} }} / {{ maxValue(usageInfo?.maxJsExecutions) }}</div>
<div class="tb-usage-item-counts" [class.critical]="apiCallItemCritical.alarms">{{ usageInfo?.alarms | shortNumber: {roundDown: true} }} / {{ maxValue(usageInfo?.maxAlarms) }}</div>
<div class="tb-usage-item-counts" [class.critical]="apiCallItemCritical.emails">{{ usageInfo?.emails | shortNumber: {roundDown: true} }} / {{ maxValue(usageInfo?.maxEmails) }}</div>
<div class="tb-usage-item-counts" [class.critical]="apiCallItemCritical.sms">{{ usageInfo?.sms | shortNumber: {roundDown: true} }} / {{ maxValue(usageInfo?.maxSms) }}</div>
</div>
<div class="tb-usage-items-progress">
<mat-progress-bar [class.critical]="apiCallItemCritical.transportMessages" color="primary" mode="determinate" [value]="progressValue(usageInfo?.transportMessages, usageInfo?.maxTransportMessages)"></mat-progress-bar>

Loading…
Cancel
Save