Merge branch 'rc'

This commit is contained in:
Igor Kulikov 2025-01-22 18:25:11 +02:00
commit f920010786
7 changed files with 29 additions and 16 deletions

View File

@ -66,7 +66,9 @@ import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.ThreadLocalRandom;
@ -78,6 +80,7 @@ import java.util.stream.Collectors;
@Slf4j @Slf4j
public class EdgeImitator { public class EdgeImitator {
private static final int MAX_DOWNLINK_FAILS = 2;
private final String routingKey; private final String routingKey;
private final String routingSecret; private final String routingSecret;
@ -93,6 +96,7 @@ public class EdgeImitator {
private boolean randomFailuresOnTimeseriesDownlink = false; private boolean randomFailuresOnTimeseriesDownlink = false;
@Setter @Setter
private double failureProbability = 0.0; private double failureProbability = 0.0;
private final Map<Integer, Integer> downlinkFailureCountMap = new HashMap<>();
@Getter @Getter
private EdgeConfiguration configuration; private EdgeConfiguration configuration;
@ -244,8 +248,11 @@ public class EdgeImitator {
if (downlinkMsg.getEntityDataCount() > 0) { if (downlinkMsg.getEntityDataCount() > 0) {
for (EntityDataProto entityData : downlinkMsg.getEntityDataList()) { for (EntityDataProto entityData : downlinkMsg.getEntityDataList()) {
if (randomFailuresOnTimeseriesDownlink) { if (randomFailuresOnTimeseriesDownlink) {
if (getRandomBoolean()) { int downlinkMsgId = downlinkMsg.getDownlinkMsgId();
if (getRandomBoolean() && checkFailureThreshold(downlinkMsgId)) {
result.add(Futures.immediateFailedFuture(new RuntimeException("Random failure. This is expected error for edge test"))); result.add(Futures.immediateFailedFuture(new RuntimeException("Random failure. This is expected error for edge test")));
downlinkFailureCountMap.put(downlinkMsgId, downlinkFailureCountMap.getOrDefault(downlinkMsgId, 0) + 1);
} else { } else {
result.add(saveDownlinkMsg(entityData)); result.add(saveDownlinkMsg(entityData));
} }
@ -354,6 +361,12 @@ public class EdgeImitator {
return Futures.allAsList(result); return Futures.allAsList(result);
} }
private boolean checkFailureThreshold(int downlinkMsgId) {
return failureProbability == 100 ||
downlinkFailureCountMap.get(downlinkMsgId) == null ||
downlinkFailureCountMap.get(downlinkMsgId) < MAX_DOWNLINK_FAILS;
}
private boolean getRandomBoolean() { private boolean getRandomBoolean() {
double randomValue = ThreadLocalRandom.current().nextDouble() * 100; double randomValue = ThreadLocalRandom.current().nextDouble() * 100;
return randomValue <= this.failureProbability; return randomValue <= this.failureProbability;

View File

@ -109,7 +109,7 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C
if (offset !== 0 && isNumeric(value)) { if (offset !== 0 && isNumeric(value)) {
lowerLeft = api.coord([startTime, Number(value) >= 0 ? Number(value) + offset : offset]); lowerLeft = api.coord([startTime, Number(value) >= 0 ? Number(value) + offset : offset]);
} else { } else {
lowerLeft = api.coord([startTime, value]); lowerLeft = api.coord([startTime, Number(value) >= 0 ? Number(value) : 0]);
} }
const size = api.size([delta, value]); const size = api.size([delta, value]);
const width = size[0]; const width = size[0];

View File

@ -260,7 +260,7 @@ export const parseWithTranslation = {
export function functionValueCalculator<T>(useFunction: boolean, func: CompiledTbFunction<GenericFunction>, params = [], defaultValue: T): T { export function functionValueCalculator<T>(useFunction: boolean, func: CompiledTbFunction<GenericFunction>, params = [], defaultValue: T): T {
let res: T; let res: T;
if (useFunction && isDefined(func) && isFunction(func)) { if (useFunction && isDefinedAndNotNull(func)) {
try { try {
res = func.execute(...params); res = func.execute(...params);
if (!isDefinedAndNotNull(res) || res === '') { if (!isDefinedAndNotNull(res) || res === '') {

View File

@ -357,7 +357,7 @@ export default abstract class LeafletMap {
this.map.pm.Toolbar.copyDrawControl('Circle', { this.map.pm.Toolbar.copyDrawControl('Circle', {
name: 'tbCircle', name: 'tbCircle',
afterClick: () => this.selectEntityWithoutLocation('tbCircle'), afterClick: () => this.selectEntityWithoutLocation('tbCircle'),
disabled: true, disabled: false,
actions actions
}); });
} }

View File

@ -21,20 +21,20 @@ import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { import {
CircleSettings, CircleSettings,
CommonMapSettings,
defaultCircleSettings, defaultCircleSettings,
defaultCommonMapSettings,
defaultMapProviderSettings, defaultMapProviderSettings,
defaultMarkersSettings,
defaultPolygonSettings, defaultPolygonSettings,
defaultTripAnimationCommonSettings,
defaultTripAnimationMarkersSettings,
defaultTripAnimationPathSettings, defaultTripAnimationPathSettings,
defaultTripAnimationPointSettings, defaultTripAnimationPointSettings,
defaultTripAnimationSettings, defaultTripAnimationSettings,
MapProviderSettings, MapProviderSettings,
MarkersSettings,
PointsSettings, PointsSettings,
PolygonSettings, PolygonSettings,
PolylineSettings PolylineSettings,
TripAnimationCommonSettings,
TripAnimationMarkerSettings
} from 'src/app/modules/home/components/widget/lib/maps/map-models'; } from 'src/app/modules/home/components/widget/lib/maps/map-models';
import { extractType } from '@core/utils'; import { extractType } from '@core/utils';
@ -76,8 +76,8 @@ export class TripAnimationWidgetSettingsComponent extends WidgetSettingsComponen
protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { protected prepareInputSettings(settings: WidgetSettings): WidgetSettings {
const mapProviderSettings = extractType<MapProviderSettings>(settings, Object.keys(defaultMapProviderSettings) as (keyof MapProviderSettings)[]); const mapProviderSettings = extractType<MapProviderSettings>(settings, Object.keys(defaultMapProviderSettings) as (keyof MapProviderSettings)[]);
const commonMapSettings = extractType<CommonMapSettings>(settings, Object.keys(defaultCommonMapSettings) as (keyof CommonMapSettings)[]); const commonMapSettings = extractType<TripAnimationCommonSettings>(settings, Object.keys(defaultTripAnimationCommonSettings) as (keyof TripAnimationCommonSettings)[]);
const markersSettings = extractType<MarkersSettings>(settings, Object.keys(defaultMarkersSettings) as (keyof MarkersSettings)[]); const markersSettings = extractType<TripAnimationMarkerSettings>(settings, Object.keys(defaultTripAnimationMarkersSettings) as (keyof TripAnimationMarkerSettings)[]);
const pathSettings = extractType<PolylineSettings>(settings, Object.keys(defaultTripAnimationPathSettings) as (keyof PolylineSettings)[]); const pathSettings = extractType<PolylineSettings>(settings, Object.keys(defaultTripAnimationPathSettings) as (keyof PolylineSettings)[]);
const pointSettings = extractType<PointsSettings>(settings, Object.keys(defaultTripAnimationPointSettings) as (keyof PointsSettings)[]); const pointSettings = extractType<PointsSettings>(settings, Object.keys(defaultTripAnimationPointSettings) as (keyof PointsSettings)[]);
const polygonSettings = extractType<PolygonSettings>(settings, Object.keys(defaultPolygonSettings) as (keyof PolygonSettings)[]); const polygonSettings = extractType<PolygonSettings>(settings, Object.keys(defaultPolygonSettings) as (keyof PolygonSettings)[]);

View File

@ -107,7 +107,7 @@
<mat-label translate>mobile.latest-version</mat-label> <mat-label translate>mobile.latest-version</mat-label>
<input matInput formControlName="latestVersion"> <input matInput formControlName="latestVersion">
<mat-hint> </mat-hint> <mat-hint> </mat-hint>
<mat-error *ngIf="entityForm.get('versionInfo.minVersion').hasError('pattern')"> <mat-error *ngIf="entityForm.get('versionInfo.latestVersion').hasError('pattern')">
{{ 'mobile.invalid-version-pattern' | translate }} {{ 'mobile.invalid-version-pattern' | translate }}
</mat-error> </mat-error>
</mat-form-field> </mat-form-field>
@ -130,7 +130,7 @@
<mat-label>{{ <mat-label>{{
(entityForm.get('platformType').value === PlatformType.ANDROID ? 'mobile.google-play-link' : 'mobile.app-store-link') | translate (entityForm.get('platformType').value === PlatformType.ANDROID ? 'mobile.google-play-link' : 'mobile.app-store-link') | translate
}}</mat-label> }}</mat-label>
<input matInput [required]="entityForm.get('status').value === 'PUBLISHED'" formControlName="storeLink"> <input matInput [required]="entityForm.get('status').value === MobileAppStatus.PUBLISHED" formControlName="storeLink">
<tb-copy-button <tb-copy-button
matSuffix matSuffix
miniButton="false" miniButton="false"
@ -148,7 +148,7 @@
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline" *ngIf="entityForm.get('platformType').value === PlatformType.ANDROID"> <mat-form-field appearance="outline" *ngIf="entityForm.get('platformType').value === PlatformType.ANDROID">
<mat-label translate>mobile.sha256-certificate-fingerprints</mat-label> <mat-label translate>mobile.sha256-certificate-fingerprints</mat-label>
<input matInput [required]="entityForm.get('status').value !== MobileAppStatus.DRAFT" formControlName="sha256CertFingerprints"> <input matInput [required]="entityForm.get('status').value === MobileAppStatus.PUBLISHED" formControlName="sha256CertFingerprints">
<tb-copy-button <tb-copy-button
matSuffix matSuffix
miniButton="false" miniButton="false"
@ -166,7 +166,7 @@
</mat-form-field> </mat-form-field>
<mat-form-field appearance="outline" *ngIf="entityForm.get('platformType').value === PlatformType.IOS"> <mat-form-field appearance="outline" *ngIf="entityForm.get('platformType').value === PlatformType.IOS">
<mat-label translate>mobile.app-id</mat-label> <mat-label translate>mobile.app-id</mat-label>
<input matInput [required]="entityForm.get('status').value !== MobileAppStatus.DRAFT" formControlName="appId"> <input matInput [required]="entityForm.get('status').value === MobileAppStatus.PUBLISHED" formControlName="appId">
<tb-copy-button <tb-copy-button
matSuffix matSuffix
miniButton="false" miniButton="false"

View File

@ -101,7 +101,7 @@ export class MobileAppComponent extends EntityComponent<MobileApp> {
form.get('status').valueChanges.pipe( form.get('status').valueChanges.pipe(
takeUntilDestroyed() takeUntilDestroyed()
).subscribe((value: MobileAppStatus) => { ).subscribe((value: MobileAppStatus) => {
if (value !== MobileAppStatus.DRAFT) { if (value === MobileAppStatus.PUBLISHED) {
form.get('storeInfo.storeLink').addValidators(Validators.required); form.get('storeInfo.storeLink').addValidators(Validators.required);
form.get('storeInfo.sha256CertFingerprints') form.get('storeInfo.sha256CertFingerprints')
.addValidators(Validators.required); .addValidators(Validators.required);