diff --git a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java index 45dfed1bcb..890ceb373f 100644 --- a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java +++ b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java @@ -66,7 +66,9 @@ import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; import java.lang.reflect.Field; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ThreadLocalRandom; @@ -78,6 +80,7 @@ import java.util.stream.Collectors; @Slf4j public class EdgeImitator { + private static final int MAX_DOWNLINK_FAILS = 2; private final String routingKey; private final String routingSecret; @@ -93,6 +96,7 @@ public class EdgeImitator { private boolean randomFailuresOnTimeseriesDownlink = false; @Setter private double failureProbability = 0.0; + private final Map downlinkFailureCountMap = new HashMap<>(); @Getter private EdgeConfiguration configuration; @@ -244,8 +248,11 @@ public class EdgeImitator { if (downlinkMsg.getEntityDataCount() > 0) { for (EntityDataProto entityData : downlinkMsg.getEntityDataList()) { 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"))); + downlinkFailureCountMap.put(downlinkMsgId, downlinkFailureCountMap.getOrDefault(downlinkMsgId, 0) + 1); } else { result.add(saveDownlinkMsg(entityData)); } @@ -354,6 +361,12 @@ public class EdgeImitator { 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() { double randomValue = ThreadLocalRandom.current().nextDouble() * 100; return randomValue <= this.failureProbability; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts index c648dceb2a..1f4a07907e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/chart/time-series-chart-bar.models.ts @@ -109,7 +109,7 @@ export const renderTimeSeriesBar = (params: CustomSeriesRenderItemParams, api: C if (offset !== 0 && isNumeric(value)) { lowerLeft = api.coord([startTime, Number(value) >= 0 ? Number(value) + offset : offset]); } else { - lowerLeft = api.coord([startTime, value]); + lowerLeft = api.coord([startTime, Number(value) >= 0 ? Number(value) : 0]); } const size = api.size([delta, value]); const width = size[0]; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts index cd974ee22c..2d466803eb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/common-maps-utils.ts @@ -260,7 +260,7 @@ export const parseWithTranslation = { export function functionValueCalculator(useFunction: boolean, func: CompiledTbFunction, params = [], defaultValue: T): T { let res: T; - if (useFunction && isDefined(func) && isFunction(func)) { + if (useFunction && isDefinedAndNotNull(func)) { try { res = func.execute(...params); if (!isDefinedAndNotNull(res) || res === '') { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index ee5af0bfb5..f74acd9f2c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -357,7 +357,7 @@ export default abstract class LeafletMap { this.map.pm.Toolbar.copyDrawControl('Circle', { name: 'tbCircle', afterClick: () => this.selectEntityWithoutLocation('tbCircle'), - disabled: true, + disabled: false, actions }); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/trip-animation-widget-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/trip-animation-widget-settings.component.ts index ab4ab92e17..88316d185c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/trip-animation-widget-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/trip-animation-widget-settings.component.ts @@ -21,20 +21,20 @@ import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { CircleSettings, - CommonMapSettings, defaultCircleSettings, - defaultCommonMapSettings, defaultMapProviderSettings, - defaultMarkersSettings, defaultPolygonSettings, + defaultTripAnimationCommonSettings, + defaultTripAnimationMarkersSettings, defaultTripAnimationPathSettings, defaultTripAnimationPointSettings, defaultTripAnimationSettings, MapProviderSettings, - MarkersSettings, PointsSettings, PolygonSettings, - PolylineSettings + PolylineSettings, + TripAnimationCommonSettings, + TripAnimationMarkerSettings } from 'src/app/modules/home/components/widget/lib/maps/map-models'; import { extractType } from '@core/utils'; @@ -76,8 +76,8 @@ export class TripAnimationWidgetSettingsComponent extends WidgetSettingsComponen protected prepareInputSettings(settings: WidgetSettings): WidgetSettings { const mapProviderSettings = extractType(settings, Object.keys(defaultMapProviderSettings) as (keyof MapProviderSettings)[]); - const commonMapSettings = extractType(settings, Object.keys(defaultCommonMapSettings) as (keyof CommonMapSettings)[]); - const markersSettings = extractType(settings, Object.keys(defaultMarkersSettings) as (keyof MarkersSettings)[]); + const commonMapSettings = extractType(settings, Object.keys(defaultTripAnimationCommonSettings) as (keyof TripAnimationCommonSettings)[]); + const markersSettings = extractType(settings, Object.keys(defaultTripAnimationMarkersSettings) as (keyof TripAnimationMarkerSettings)[]); const pathSettings = extractType(settings, Object.keys(defaultTripAnimationPathSettings) as (keyof PolylineSettings)[]); const pointSettings = extractType(settings, Object.keys(defaultTripAnimationPointSettings) as (keyof PointsSettings)[]); const polygonSettings = extractType(settings, Object.keys(defaultPolygonSettings) as (keyof PolygonSettings)[]); diff --git a/ui-ngx/src/app/modules/home/pages/mobile/applications/mobile-app.component.html b/ui-ngx/src/app/modules/home/pages/mobile/applications/mobile-app.component.html index 36f52e8b71..130799776e 100644 --- a/ui-ngx/src/app/modules/home/pages/mobile/applications/mobile-app.component.html +++ b/ui-ngx/src/app/modules/home/pages/mobile/applications/mobile-app.component.html @@ -107,7 +107,7 @@ mobile.latest-version - + {{ 'mobile.invalid-version-pattern' | translate }} @@ -130,7 +130,7 @@ {{ (entityForm.get('platformType').value === PlatformType.ANDROID ? 'mobile.google-play-link' : 'mobile.app-store-link') | translate }} - + mobile.sha256-certificate-fingerprints - + mobile.app-id - + { form.get('status').valueChanges.pipe( takeUntilDestroyed() ).subscribe((value: MobileAppStatus) => { - if (value !== MobileAppStatus.DRAFT) { + if (value === MobileAppStatus.PUBLISHED) { form.get('storeInfo.storeLink').addValidators(Validators.required); form.get('storeInfo.sha256CertFingerprints') .addValidators(Validators.required);