From 7bf0cb90fa1415fdcdbaea22525e830a561cc532 Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Mon, 19 Oct 2020 12:03:28 +0300 Subject: [PATCH 01/97] Log telemetry update event to populate it into rule chain --- .../server/controller/BaseController.java | 41 +++++++++++++------ .../controller/TelemetryController.java | 10 +++++ .../server/common/data/DataConstants.java | 1 + .../server/common/data/audit/ActionType.java | 1 + 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index b8fe956217..063930f1cc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -35,7 +35,6 @@ import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; @@ -54,6 +53,8 @@ import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.page.TimePageLink; @@ -710,6 +711,9 @@ public abstract class BaseController { case PROVISION_FAILURE: msgType = DataConstants.PROVISION_FAILURE; break; + case TIMESERIES_UPDATED: + msgType = DataConstants.TIMESERIES_UPDATED; + break; } if (!StringUtils.isEmpty(msgType)) { try { @@ -754,17 +758,7 @@ public abstract class BaseController { metaData.putValue("scope", scope); if (attributes != null) { for (AttributeKvEntry attr : attributes) { - if (attr.getDataType() == DataType.BOOLEAN) { - entityNode.put(attr.getKey(), attr.getBooleanValue().get()); - } else if (attr.getDataType() == DataType.DOUBLE) { - entityNode.put(attr.getKey(), attr.getDoubleValue().get()); - } else if (attr.getDataType() == DataType.LONG) { - entityNode.put(attr.getKey(), attr.getLongValue().get()); - } else if (attr.getDataType() == DataType.JSON) { - entityNode.set(attr.getKey(), json.readTree(attr.getJsonValue().get())); - } else { - entityNode.put(attr.getKey(), attr.getValueAsString()); - } + addKvEntry(entityNode, attr); } } } else if (actionType == ActionType.ATTRIBUTES_DELETED) { @@ -775,6 +769,13 @@ public abstract class BaseController { if (keys != null) { keys.forEach(attrsArrayNode::add); } + } else if (actionType == ActionType.TIMESERIES_UPDATED) { + List telemetry = extractParameter(List.class, 0, additionalInfo); + if (telemetry != null) { + for (TsKvEntry tsKvEntry : telemetry) { + addKvEntry(entityNode, tsKvEntry); + } + } } } TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); @@ -791,6 +792,22 @@ public abstract class BaseController { } } + private void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) throws Exception { + if (kvEntry.getDataType() == DataType.BOOLEAN) { + kvEntry.getBooleanValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); + } else if (kvEntry.getDataType() == DataType.DOUBLE) { + kvEntry.getDoubleValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); + } else if (kvEntry.getDataType() == DataType.LONG) { + kvEntry.getLongValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); + } else if (kvEntry.getDataType() == DataType.JSON) { + if (kvEntry.getJsonValue().isPresent()) { + entityNode.set(kvEntry.getKey(), json.readTree(kvEntry.getJsonValue().get())); + } + } else { + entityNode.put(kvEntry.getKey(), kvEntry.getValueAsString()); + } + } + private T extractParameter(Class clazz, int index, Object... additionalInfo) { T result = null; if (additionalInfo != null && additionalInfo.length > index) { diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index c8c41ea479..abb96feffc 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -440,11 +440,13 @@ public class TelemetryController extends BaseController { tsSubService.saveAndNotify(tenantId, entityId, entries, ttl, new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { + logTelemetryUpdated(user, entityId, entries, null); result.setResult(new ResponseEntity(HttpStatus.OK)); } @Override public void onFailure(Throwable t) { + logTelemetryUpdated(user, entityId, entries, t); AccessValidator.handleError(t, result, HttpStatus.INTERNAL_SERVER_ERROR); } }); @@ -585,6 +587,14 @@ public class TelemetryController extends BaseController { } } + private void logTelemetryUpdated(SecurityUser user, EntityId entityId, List telemetry, Throwable e) { + try { + logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.TIMESERIES_UPDATED, toException(e), telemetry); + } catch (ThingsboardException te) { + log.warn("Failed to log telemetry update"); + } + } + private void logAttributesDeleted(SecurityUser user, EntityId entityId, String scope, List keys, Throwable e) { try { logEntityAction(user, (UUIDBased & EntityId) entityId, null, null, ActionType.ATTRIBUTES_DELETED, toException(e), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 5aadca44ec..0c824ed77d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -59,6 +59,7 @@ public class DataConstants { public static final String ENTITY_UNASSIGNED = "ENTITY_UNASSIGNED"; public static final String ATTRIBUTES_UPDATED = "ATTRIBUTES_UPDATED"; public static final String ATTRIBUTES_DELETED = "ATTRIBUTES_DELETED"; + public static final String TIMESERIES_UPDATED = "TIMESERIES_UPDATED"; public static final String ALARM_ACK = "ALARM_ACK"; public static final String ALARM_CLEAR = "ALARM_CLEAR"; public static final String ENTITY_ASSIGNED_FROM_TENANT = "ENTITY_ASSIGNED_FROM_TENANT"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java index e30e20090d..70677915b1 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java @@ -25,6 +25,7 @@ public enum ActionType { ATTRIBUTES_UPDATED(false), // log attributes/values ATTRIBUTES_DELETED(false), // log attributes TIMESERIES_DELETED(false), // log timeseries + TIMESERIES_UPDATED(false), // log timeseries update RPC_CALL(false), // log method and params CREDENTIALS_UPDATED(false), // log new credentials ASSIGNED_TO_CUSTOMER(false), // log customer name From 0fc43c3bd4b1b5623397acc93bbe987e2a06381f Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Fri, 23 Oct 2020 11:42:00 +0300 Subject: [PATCH 02/97] Add 'Timeseries Updated' into message type switch node --- .../thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java | 4 +++- ui-ngx/src/app/shared/models/rule-node.models.ts | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java index c5739e24ff..f3fb493345 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbMsgTypeSwitchNode.java @@ -35,7 +35,7 @@ import org.thingsboard.server.common.msg.session.SessionMsgType; configClazz = EmptyNodeConfiguration.class, relationTypes = {"Post attributes", "Post telemetry", "RPC Request from Device", "RPC Request to Device", "Activity Event", "Inactivity Event", "Connect Event", "Disconnect Event", "Entity Created", "Entity Updated", "Entity Deleted", "Entity Assigned", - "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other", "Entity Assigned From Tenant", "Entity Assigned To Tenant"}, + "Entity Unassigned", "Attributes Updated", "Attributes Deleted", "Alarm Acknowledged", "Alarm Cleared", "Other", "Entity Assigned From Tenant", "Entity Assigned To Tenant", "Timeseries Updated"}, nodeDescription = "Route incoming messages by Message Type", nodeDetails = "Sends messages with message types \"Post attributes\", \"Post telemetry\", \"RPC Request\" etc. via corresponding chain, otherwise Other chain is used.", uiResources = {"static/rulenode/rulenode-core-config.js"}, @@ -90,6 +90,8 @@ public class TbMsgTypeSwitchNode implements TbNode { relationType = "Entity Assigned From Tenant"; } else if (msg.getType().equals(DataConstants.ENTITY_ASSIGNED_TO_TENANT)) { relationType = "Entity Assigned To Tenant"; + } else if (msg.getType().equals(DataConstants.TIMESERIES_UPDATED)) { + relationType = "Timeseries updated"; } else { relationType = "Other"; } diff --git a/ui-ngx/src/app/shared/models/rule-node.models.ts b/ui-ngx/src/app/shared/models/rule-node.models.ts index b9494fb1c4..231a4b82df 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -348,7 +348,8 @@ export enum MessageType { ENTITY_ASSIGNED = 'ENTITY_ASSIGNED', ENTITY_UNASSIGNED = 'ENTITY_UNASSIGNED', ATTRIBUTES_UPDATED = 'ATTRIBUTES_UPDATED', - ATTRIBUTES_DELETED = 'ATTRIBUTES_DELETED' + ATTRIBUTES_DELETED = 'ATTRIBUTES_DELETED', + TIMESERIES_UPDATED = 'TIMESERIES_UPDATED' } export const messageTypeNames = new Map( @@ -367,7 +368,8 @@ export const messageTypeNames = new Map( [MessageType.ENTITY_ASSIGNED, 'Entity Assigned'], [MessageType.ENTITY_UNASSIGNED, 'Entity Unassigned'], [MessageType.ATTRIBUTES_UPDATED, 'Attributes Updated'], - [MessageType.ATTRIBUTES_DELETED, 'Attributes Deleted'] + [MessageType.ATTRIBUTES_DELETED, 'Attributes Deleted'], + [MessageType.TIMESERIES_UPDATED, 'Timeseries Updated'] ] ); From 643563ec6a8cdce5df02b1e4adfc8d7a8d9c00ae Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Wed, 11 Nov 2020 13:24:10 +0200 Subject: [PATCH 03/97] bug-fix: trip animation for multiple devices --- .../components/widget/lib/maps/leaflet-map.ts | 38 +++--- .../trip-animation.component.ts | 111 ++++++++++++------ 2 files changed, 99 insertions(+), 50 deletions(-) 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 5d6f39850e..6944e4d23d 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 @@ -599,26 +599,32 @@ export default abstract class LeafletMap { return polygon; } - updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) { - if (this.points) { + updatePoints(pointsData: FormattedData[][], getTooltip: (point: FormattedData[], setTooltip?: boolean) => string) { + for(let i = 0; i < pointsData.length; i++) { + let pointsList = pointsData[i]; + if(i === 0) { + if (this.points) { this.map.removeLayer(this.points); + } + this.points = new FeatureGroup(); } - this.points = new FeatureGroup(); - pointsData.filter(pdata => !!this.convertPosition(pdata)).forEach(data => { - const point = L.circleMarker(this.convertPosition(data), { - color: this.options.pointColor, - radius: this.options.pointSize - }); - if (!this.options.pointTooltipOnRightPanel) { - point.on('click', () => getTooltip(data)); - } - else { - createTooltip(point, this.options, data.$datasource, getTooltip(data, false)); - } - this.points.addLayer(point); + pointsList.filter(pdata => !!this.convertPosition(pdata)).forEach(data => { + const point = L.circleMarker(this.convertPosition(data), { + color: this.options.pointColor, + radius: this.options.pointSize + }); + if (!this.options.pointTooltipOnRightPanel) { + point.on('click', () => getTooltip([data])); + } else { + createTooltip(point, this.options, data.$datasource, getTooltip([data], false)); + } + this.points.addLayer(point); }); - this.map.addLayer(this.points); + if(i === 0) { + this.map.addLayer(this.points); + } } + } // Polyline diff --git a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts index 99c399d3f2..b51cda6cbb 100644 --- a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts @@ -47,6 +47,9 @@ import moment from 'moment'; import { isUndefined } from '@core/utils'; import { ResizeObserver } from '@juggle/resize-observer'; +interface dataMap { + [key: string] : FormattedData +} @Component({ // tslint:disable-next-line:component-selector @@ -70,7 +73,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy interpolatedTimeData = []; widgetConfig: WidgetConfig; settings: TripAnimationSettings; - mainTooltip = ''; + mainTooltips = []; visibleTooltip = false; activeTrip: FormattedData; label: string; @@ -115,7 +118,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy this.historicalData = parseArray(this.ctx.data).filter(arr => arr.length); if (this.historicalData.length) { this.calculateIntervals(); - this.timeUpdated(this.currentTime && this.currentTime > this.minTime ? this.currentTime : this.minTime); + this.timeUpdated(this.minTime); } this.mapWidget.map.map?.invalidateSize(); this.cd.detectChanges(); @@ -138,34 +141,41 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy timeUpdated(time: number) { this.currentTime = time; - const currentPosition = this.interpolatedTimeData + let currentPosition = this.interpolatedTimeData .map(dataSource => dataSource[time]) - .filter(ds => ds); - if (isUndefined(currentPosition[0])) { - const timePoints = Object.keys(this.interpolatedTimeData[0]).map(item => parseInt(item, 10)); - for (let i = 1; i < timePoints.length; i++) { - if (timePoints[i - 1] < time && timePoints[i] > time) { - const beforePosition = this.interpolatedTimeData[0][timePoints[i - 1]]; - const afterPosition = this.interpolatedTimeData[0][timePoints[i]]; - const ratio = getRatio(timePoints[i - 1], timePoints[i], time); - currentPosition[0] = { - ...beforePosition, - time, - ...interpolateOnLineSegment(beforePosition, afterPosition, this.settings.latKeyName, this.settings.lngKeyName, ratio) + for(let j = 0; j < this.interpolatedTimeData.length; j++) { + if (isUndefined(currentPosition[j])) { + const timePoints = Object.keys(this.interpolatedTimeData[j]).map(item => parseInt(item, 10)); + for (let i = 1; i < timePoints.length; i++) { + if (timePoints[i - 1] < time && timePoints[i] > time) { + const beforePosition = this.interpolatedTimeData[j][timePoints[i - 1]]; + const afterPosition = this.interpolatedTimeData[j][timePoints[i]]; + const ratio = getRatio(timePoints[i - 1], timePoints[i], time); + currentPosition[j] = { + ...beforePosition, + time, + ...interpolateOnLineSegment(beforePosition, afterPosition, this.settings.latKeyName, this.settings.lngKeyName, ratio) + } + break; } - break; } } } - this.calcLabel(); - this.calcTooltip(currentPosition.find(position => position.entityName === this.activeTrip.entityName)); + for(let j = 0; j < this.interpolatedTimeData.length; j++) { + if (isUndefined(currentPosition[j])) { + currentPosition[j] = this.calculateLastPoints(this.interpolatedTimeData[j], time); + } + } + this.calcLabel(currentPosition); + this.calcTooltip(currentPosition, true); if (this.mapWidget && this.mapWidget.map && this.mapWidget.map.map) { - this.mapWidget.map.updatePolylines(this.interpolatedTimeData.map(ds => _.values(ds)), true, this.activeTrip); + const formattedInterpolatedTimeData = this.interpolatedTimeData.map(ds => _.values(ds)); + this.mapWidget.map.updatePolylines(formattedInterpolatedTimeData, true); if (this.settings.showPolygon) { this.mapWidget.map.updatePolygons(this.interpolatedTimeData); } if (this.settings.showPoints) { - this.mapWidget.map.updatePoints(_.values(_.union(this.interpolatedTimeData)[0]), this.calcTooltip); + this.mapWidget.map.updatePoints(formattedInterpolatedTimeData, this.calcTooltip); } this.mapWidget.map.updateMarkers(currentPosition, true, (trip) => { this.activeTrip = trip; @@ -177,6 +187,23 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy setActiveTrip() { } + private calculateLastPoints(dataSource: dataMap, time: number): FormattedData { + const timeArr = Object.keys(dataSource); + let index = timeArr.findIndex((dtime, index) => { + return Number(dtime) >= time; + }); + + if(index !== -1) { + if(Number(timeArr[index]) !== time && index !== 0) { + index--; + } + } else { + index = timeArr.length - 1; + } + + return dataSource[timeArr[index]]; + } + calculateIntervals() { this.historicalData.forEach((dataSource, index) => { this.minTime = dataSource[0]?.time || Infinity; @@ -194,23 +221,39 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy } } - calcTooltip = (point?: FormattedData): string => { - const data = point ? point : this.activeTrip; - const tooltipPattern: string = this.settings.useTooltipFunction ? - safeExecute(this.settings.tooltipFunction, [data, this.historicalData, point.dsIndex]) : this.settings.tooltipPattern; - const tooltipText = parseWithTranslation.parseTemplate(tooltipPattern, data, true); - this.mainTooltip = this.sanitizer.sanitize( - SecurityContext.HTML, tooltipText); - this.cd.detectChanges(); - this.activeTrip = point; + calcTooltip = (points?: FormattedData[], isMainTooltip: boolean = false): string => { + let tooltipText; + if(isMainTooltip) { + this.mainTooltips = [] + } + for (let point of points) { + const data = point ? point : this.activeTrip; + const tooltipPattern: string = this.settings.useTooltipFunction ? + safeExecute(this.settings.tooltipFunction, [data, this.historicalData, point.dsIndex]) : this.settings.tooltipPattern; + tooltipText = parseWithTranslation.parseTemplate(tooltipPattern, data, true); + if(isMainTooltip) { + this.mainTooltips.push(this.sanitizer.sanitize(SecurityContext.HTML, tooltipText)); + } + this.cd.detectChanges(); + this.activeTrip = point; + } return tooltipText; } - calcLabel() { - const data = this.activeTrip; - const labelText: string = this.settings.useLabelFunction ? - safeExecute(this.settings.labelFunction, [data, this.historicalData, data.dsIndex]) : this.settings.label; - this.label = (parseWithTranslation.parseTemplate(labelText, data, true)); + calcLabel(formattedDataArr: FormattedData[]) { + // const data = this.activeTrip; + // const labelText: string = this.settings.useLabelFunction ? + // safeExecute(this.settings.labelFunction, [data, this.historicalData, data.dsIndex]) : this.settings.label; + // this.label = (parseWithTranslation.parseTemplate(labelText, data, true)); + // console.log(this.label, 'this.label'); + this.label = ''; + for (let formattedData of formattedDataArr) { + const data = formattedData; + const labelText: string = this.settings.useLabelFunction ? + safeExecute(this.settings.labelFunction, [data, this.historicalData, data.dsIndex]) : this.settings.label; + const label = (parseWithTranslation.parseTemplate(labelText, data, true)); + this.label = this.label.length ? this.label + ',' + label : label; + } } interpolateArray(originData: FormattedData[]) { From 33b1cee2d018a277d7179e3d6fcd4600f0dd99dc Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Wed, 11 Nov 2020 13:27:18 +0200 Subject: [PATCH 04/97] bug-fix: trip animation for multiple devices --- .../widget/trip-animation/trip-animation.component.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts index b51cda6cbb..a392481259 100644 --- a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts @@ -241,11 +241,6 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy } calcLabel(formattedDataArr: FormattedData[]) { - // const data = this.activeTrip; - // const labelText: string = this.settings.useLabelFunction ? - // safeExecute(this.settings.labelFunction, [data, this.historicalData, data.dsIndex]) : this.settings.label; - // this.label = (parseWithTranslation.parseTemplate(labelText, data, true)); - // console.log(this.label, 'this.label'); this.label = ''; for (let formattedData of formattedDataArr) { const data = formattedData; From 2e624994da5da91f42b127f02bb6777432cba433 Mon Sep 17 00:00:00 2001 From: Dmitriymush Date: Wed, 11 Nov 2020 14:38:24 +0200 Subject: [PATCH 05/97] additional fix to component html --- .../widget/trip-animation/trip-animation.component.html | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html index 2ea80cf2af..be82969986 100644 --- a/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.html @@ -28,8 +28,12 @@
+ [ngClass]="{'trip-animation-tooltip-hidden':!visibleTooltip}" + [ngStyle]="{'background-color': settings.tooltipColor, 'opacity': settings.tooltipOpacity, 'color': settings.tooltipFontColor}"> +
+
Date: Wed, 11 Nov 2020 16:22:57 +0200 Subject: [PATCH 06/97] added # filter topic handling --- .../transport/mqtt/util/MqttTopicFilterFactory.java | 13 +++++++++---- .../mqtt/util/MqttTopicFilterFactoryTest.java | 11 ++++++++++- 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java index 4d5a9a7c2b..9893f8cbef 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java @@ -34,10 +34,15 @@ public class MqttTopicFilterFactory { } return filters.computeIfAbsent(topicFilter, filter -> { if (filter.contains("+") || filter.contains("#")) { - String regex = filter - .replace("\\", "\\\\") - .replace("+", "[^/]+") - .replace("/#", "($|/.*)"); + String regex; + if (filter.equals("#")) { + regex = filter.replace("#", "^(?!/).+"); + } else { + regex = filter + .replace("\\", "\\\\") + .replace("+", "[^/]+") + .replace("/#", "($|/.*)"); + } log.debug("Converting [{}] to [{}]", filter, regex); return new RegexTopicFilter(regex); } else { diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java index 0b854d51ef..f3a65bda14 100644 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java @@ -20,7 +20,6 @@ import org.junit.runner.RunWith; import org.mockito.runners.MockitoJUnitRunner; import javax.script.ScriptException; -import java.util.regex.Pattern; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -32,6 +31,9 @@ public class MqttTopicFilterFactoryTest { private static String TEST_STR_2 = "Sensor/Temperature"; private static String TEST_STR_3 = "Sensor/Temperature2/House/48"; + private static String TEST_STR_4 = String.format("%s%n%s", "/Sensor/Temperature", "/House/48"); + private static String TEST_STR_5 = "/" + TEST_STR_1; + @Test public void metadataCanBeUpdated() throws ScriptException { MqttTopicFilter filter = MqttTopicFilterFactory.toFilter("Sensor/Temperature/House/+"); @@ -51,6 +53,13 @@ public class MqttTopicFilterFactoryTest { assertTrue(filter.filter(TEST_STR_1)); assertTrue(filter.filter(TEST_STR_2)); assertFalse(filter.filter(TEST_STR_3)); + + filter = MqttTopicFilterFactory.toFilter("#"); + assertTrue(filter.filter(TEST_STR_1)); + assertTrue(filter.filter(TEST_STR_2)); + assertTrue(filter.filter(TEST_STR_3)); + assertFalse(filter.filter(TEST_STR_4)); + assertFalse(filter.filter(TEST_STR_5)); } } From 4ee38a15d3c658dc6b276af95f6290f90f1f0dcf Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 11 Nov 2020 19:31:21 +0200 Subject: [PATCH 07/97] change regex for # filter --- .../transport/mqtt/util/MqttTopicFilterFactory.java | 2 +- .../mqtt/util/MqttTopicFilterFactoryTest.java | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java index 9893f8cbef..98e472ba5c 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java @@ -36,7 +36,7 @@ public class MqttTopicFilterFactory { if (filter.contains("+") || filter.contains("#")) { String regex; if (filter.equals("#")) { - regex = filter.replace("#", "^(?!/).+"); + regex = filter.replace("#", "\\S+"); } else { regex = filter .replace("\\", "\\\\") diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java index f3a65bda14..2ec05fac78 100644 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java @@ -30,9 +30,10 @@ public class MqttTopicFilterFactoryTest { private static String TEST_STR_1 = "Sensor/Temperature/House/48"; private static String TEST_STR_2 = "Sensor/Temperature"; private static String TEST_STR_3 = "Sensor/Temperature2/House/48"; - - private static String TEST_STR_4 = String.format("%s%n%s", "/Sensor/Temperature", "/House/48"); - private static String TEST_STR_5 = "/" + TEST_STR_1; + private static String TEST_STR_4 = "/Sensor/Temperature2/House/48"; + private static String TEST_STR_5 = String.format("%s%n%s", "/Sensor/Temperature", "/House/48"); + private static String TEST_STR_6 = ""; + private static String TEST_STR_7 = " "; @Test public void metadataCanBeUpdated() throws ScriptException { @@ -58,8 +59,10 @@ public class MqttTopicFilterFactoryTest { assertTrue(filter.filter(TEST_STR_1)); assertTrue(filter.filter(TEST_STR_2)); assertTrue(filter.filter(TEST_STR_3)); - assertFalse(filter.filter(TEST_STR_4)); + assertTrue(filter.filter(TEST_STR_4)); assertFalse(filter.filter(TEST_STR_5)); + assertFalse(filter.filter(TEST_STR_6)); + assertFalse(filter.filter(TEST_STR_7)); } } From f4bec22a616bb1f43ac2e51eeedda9162d880c9c Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Thu, 12 Nov 2020 19:15:40 +0200 Subject: [PATCH 08/97] Send to rule chain a timestamp when KV entries were updated --- .../org/thingsboard/server/controller/BaseController.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index b82c3f8311..b6acfdd7fd 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -771,10 +771,13 @@ public abstract class BaseController { } } else if (actionType == ActionType.TIMESERIES_UPDATED) { List telemetry = extractParameter(List.class, 0, additionalInfo); - if (telemetry != null) { + if (telemetry != null && !telemetry.isEmpty()) { + ObjectNode values = json.createObjectNode(); for (TsKvEntry tsKvEntry : telemetry) { - addKvEntry(entityNode, tsKvEntry); + addKvEntry(values, tsKvEntry); } + entityNode.put("ts", telemetry.get(0).getTs()); + entityNode.set("values", values); } } } From 6ccb9d6036b81cee83e77d9acdfb732409cf4f34 Mon Sep 17 00:00:00 2001 From: Viacheslav Kukhtyn Date: Fri, 13 Nov 2020 12:47:07 +0200 Subject: [PATCH 09/97] Send array of KV entry grouped by timestamp to rule chain --- .../server/controller/BaseController.java | 29 +++++++++++++------ 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index b6acfdd7fd..527c39bd0c 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -108,9 +108,11 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.mail.MessagingException; import javax.servlet.http.HttpServletResponse; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.stream.Collectors; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -770,15 +772,8 @@ public abstract class BaseController { keys.forEach(attrsArrayNode::add); } } else if (actionType == ActionType.TIMESERIES_UPDATED) { - List telemetry = extractParameter(List.class, 0, additionalInfo); - if (telemetry != null && !telemetry.isEmpty()) { - ObjectNode values = json.createObjectNode(); - for (TsKvEntry tsKvEntry : telemetry) { - addKvEntry(values, tsKvEntry); - } - entityNode.put("ts", telemetry.get(0).getTs()); - entityNode.set("values", values); - } + List timeseries = extractParameter(List.class, 0, additionalInfo); + addTimeseries(entityNode, timeseries); } } TbMsg tbMsg = TbMsg.newMsg(msgType, entityId, metaData, TbMsgDataType.JSON, json.writeValueAsString(entityNode)); @@ -831,4 +826,20 @@ public abstract class BaseController { return null; } + private void addTimeseries(ObjectNode entityNode, List timeseries) throws Exception { + if (timeseries != null && !timeseries.isEmpty()) { + ArrayNode result = entityNode.putArray("timeseries"); + Map> groupedTelemetry = timeseries.stream() + .collect(Collectors.groupingBy(TsKvEntry::getTs)); + for (Map.Entry> entry : groupedTelemetry.entrySet()) { + ObjectNode element = json.createObjectNode(); + element.put("ts", entry.getKey()); + ObjectNode values = element.putObject("values"); + for (TsKvEntry tsKvEntry : entry.getValue()) { + addKvEntry(values, tsKvEntry); + } + result.add(element); + } + } + } } From 3127678d17788d8e8101bda0921e4af78e2b56cb Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 16:52:24 +0200 Subject: [PATCH 10/97] added AlwaysTrueTopicFilter --- .../mqtt/util/AlwaysTrueTopicFilter.java | 27 +++++++++++++++++++ .../mqtt/util/MqttTopicFilterFactory.java | 2 +- .../mqtt/util/MqttTopicFilterFactoryTest.java | 13 ++++----- 3 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java new file mode 100644 index 0000000000..9952c4b507 --- /dev/null +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/AlwaysTrueTopicFilter.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2020 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.transport.mqtt.util; + +import lombok.Data; + +@Data +public class AlwaysTrueTopicFilter implements MqttTopicFilter { + + @Override + public boolean filter(String topic) { + return true; + } +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java index 98e472ba5c..f2b4ef27e2 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java @@ -36,7 +36,7 @@ public class MqttTopicFilterFactory { if (filter.contains("+") || filter.contains("#")) { String regex; if (filter.equals("#")) { - regex = filter.replace("#", "\\S+"); + return new AlwaysTrueTopicFilter(); } else { regex = filter .replace("\\", "\\\\") diff --git a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java index 2ec05fac78..fac2e5c01d 100644 --- a/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java +++ b/common/transport/mqtt/src/test/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactoryTest.java @@ -31,9 +31,8 @@ public class MqttTopicFilterFactoryTest { private static String TEST_STR_2 = "Sensor/Temperature"; private static String TEST_STR_3 = "Sensor/Temperature2/House/48"; private static String TEST_STR_4 = "/Sensor/Temperature2/House/48"; - private static String TEST_STR_5 = String.format("%s%n%s", "/Sensor/Temperature", "/House/48"); - private static String TEST_STR_6 = ""; - private static String TEST_STR_7 = " "; + private static String TEST_STR_5 = "Sensor/ Temperature"; + private static String TEST_STR_6 = "/"; @Test public void metadataCanBeUpdated() throws ScriptException { @@ -60,9 +59,11 @@ public class MqttTopicFilterFactoryTest { assertTrue(filter.filter(TEST_STR_2)); assertTrue(filter.filter(TEST_STR_3)); assertTrue(filter.filter(TEST_STR_4)); - assertFalse(filter.filter(TEST_STR_5)); - assertFalse(filter.filter(TEST_STR_6)); - assertFalse(filter.filter(TEST_STR_7)); + assertTrue(filter.filter(TEST_STR_5)); + assertTrue(filter.filter(TEST_STR_6)); + + filter = MqttTopicFilterFactory.toFilter("Sensor/Temperature#"); + assertFalse(filter.filter(TEST_STR_2)); } } From 22c91039553c1ef31c86a3fdfd437ff115e04320 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 16 Nov 2020 16:55:24 +0200 Subject: [PATCH 11/97] fix MqttTopicFilterFactory toFilter --- .../mqtt/util/MqttTopicFilterFactory.java | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java index f2b4ef27e2..0c3b497591 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/util/MqttTopicFilterFactory.java @@ -33,16 +33,13 @@ public class MqttTopicFilterFactory { throw new IllegalArgumentException("Topic filter can't be empty!"); } return filters.computeIfAbsent(topicFilter, filter -> { - if (filter.contains("+") || filter.contains("#")) { - String regex; - if (filter.equals("#")) { - return new AlwaysTrueTopicFilter(); - } else { - regex = filter - .replace("\\", "\\\\") - .replace("+", "[^/]+") - .replace("/#", "($|/.*)"); - } + if (filter.equals("#")) { + return new AlwaysTrueTopicFilter(); + } else if (filter.contains("+") || filter.contains("#")) { + String regex = filter + .replace("\\", "\\\\") + .replace("+", "[^/]+") + .replace("/#", "($|/.*)"); log.debug("Converting [{}] to [{}]", filter, regex); return new RegexTopicFilter(regex); } else { From b81a933ba24dfa34c5367dcaba61951c81219288 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 10 Dec 2020 12:23:45 +0200 Subject: [PATCH 12/97] UI: Added preload Material Icons font --- ui-ngx/angular.json | 5 +++++ ui-ngx/src/index.html | 2 ++ ui-ngx/src/styles.scss | 1 - 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/ui-ngx/angular.json b/ui-ngx/angular.json index 3cad30304a..6d186ee012 100644 --- a/ui-ngx/angular.json +++ b/ui-ngx/angular.json @@ -59,6 +59,11 @@ "glob": "marker-shadow.png", "input": "node_modules/leaflet/dist/images/", "output": "/" + }, + { + "glob": "**/*", + "input": "node_modules/material-design-icons/iconfont/", + "output": "/fonts" } ], "styles": [ diff --git a/ui-ngx/src/index.html b/ui-ngx/src/index.html index 4f2672a518..c591cacfe1 100644 --- a/ui-ngx/src/index.html +++ b/ui-ngx/src/index.html @@ -24,6 +24,8 @@ + +