From 02b6cf6de71212a26bc6fda51561e0fc51ee8abb Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 7 Aug 2020 14:39:46 +0300 Subject: [PATCH 01/18] Fix alarm data subscription. Maps cluster mode optimizations. --- .../app/core/api/alarm-data-subscription.ts | 6 +- ui-ngx/src/app/core/api/alarm-data.service.ts | 2 +- .../components/widget/lib/maps/leaflet-map.ts | 82 +++++++++++-------- .../components/widget/lib/maps/maps-utils.ts | 5 +- .../components/widget/lib/maps/markers.ts | 29 +++---- 5 files changed, 69 insertions(+), 55 deletions(-) diff --git a/ui-ngx/src/app/core/api/alarm-data-subscription.ts b/ui-ngx/src/app/core/api/alarm-data-subscription.ts index 3e2570896d..4d3524324e 100644 --- a/ui-ngx/src/app/core/api/alarm-data-subscription.ts +++ b/ui-ngx/src/app/core/api/alarm-data-subscription.ts @@ -31,7 +31,6 @@ import { } from '@shared/models/query/query.models'; import { SubscriptionTimewindow } from '@shared/models/time/time.models'; import { AlarmDataListener } from '@core/api/alarm-data.service'; -import { UtilsService } from '@core/services/utils.service'; import { PageData } from '@shared/models/page/page-data'; import { deepClone, isDefined, isDefinedAndNotNull, isObject } from '@core/utils'; import { simulatedAlarm } from '@shared/models/alarm.models'; @@ -68,8 +67,7 @@ export class AlarmDataSubscription { constructor(public alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions, private listener: AlarmDataListener, - private telemetryService: TelemetryService, - private utils: UtilsService) { + private telemetryService: TelemetryService) { } public unsubscribe() { @@ -166,7 +164,7 @@ export class AlarmDataSubscription { private onPageData(pageData: PageData, allowedEntities: number, totalEntities: number) { this.pageData = pageData; this.resetData(); - this.listener.alarmsLoaded(pageData, this.alarmDataSubscriptionOptions.pageLink, allowedEntities, totalEntities); + this.listener.alarmsLoaded(pageData, allowedEntities, totalEntities); } private onDataUpdate(update: Array) { diff --git a/ui-ngx/src/app/core/api/alarm-data.service.ts b/ui-ngx/src/app/core/api/alarm-data.service.ts index 87d3ee02ca..cdd9874098 100644 --- a/ui-ngx/src/app/core/api/alarm-data.service.ts +++ b/ui-ngx/src/app/core/api/alarm-data.service.ts @@ -31,7 +31,7 @@ import { deepClone } from '@core/utils'; export interface AlarmDataListener { subscriptionTimewindow?: SubscriptionTimewindow; alarmSource: Datasource; - alarmsLoaded: (pageData: PageData, pageLink: AlarmDataPageLink, allowedEntities: number, totalEntities: number) => void; + alarmsLoaded: (pageData: PageData, allowedEntities: number, totalEntities: number) => void; alarmsUpdated: (update: Array, pageData: PageData) => void; subscription?: AlarmDataSubscription; } 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 49fdd19677..bdd3f8875d 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 @@ -15,7 +15,7 @@ /// import L, { - FeatureGroup, + FeatureGroup, Icon, LatLngBounds, LatLngTuple, markerClusterGroup, @@ -59,6 +59,7 @@ export default abstract class LeafletMap { points: FeatureGroup; markersData: FormattedData[] = []; polygonsData: FormattedData[] = []; + defaultMarkerIconInfo: { size: number[], icon: Icon }; protected constructor(public ctx: WidgetContext, public $container: HTMLElement, @@ -308,7 +309,11 @@ export default abstract class LeafletMap { updateMarkers(markersData: FormattedData[], updateBounds = true, callback?) { const rawMarkers = markersData.filter(mdata => !!this.convertPosition(mdata)); this.ready$.subscribe(() => { - const keys: string[] = []; + const toDelete = new Set(Array.from(this.markers.keys())); + const createdMarkers: Marker[] = []; + const updatedMarkers: Marker[] = []; + const deletedMarkers: Marker[] = []; + let m: Marker; rawMarkers.forEach(data => { if (data.rotationAngle || data.rotationAngle === 0) { const currentImage = this.options.useMarkerImageFunction ? @@ -325,22 +330,36 @@ export default abstract class LeafletMap { this.options.icon = null; } if (this.markers.get(data.entityName)) { - this.updateMarker(data.entityName, data, markersData, this.options) + m = this.updateMarker(data.entityName, data, markersData, this.options); + if (m) { + updatedMarkers.push(m); + } } else { - this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback); - } - keys.push(data.entityName); - }); - const toDelete: string[] = []; - this.markers.forEach((v, mKey) => { - if (!keys.includes(mKey)) { - toDelete.push(mKey); + m = this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback); + if (m) { + createdMarkers.push(m); + } } + toDelete.delete(data.entityName); }); toDelete.forEach((key) => { - this.deleteMarker(key); + m = this.deleteMarker(key); + if (m) { + deletedMarkers.push(m); + } }); this.markersData = markersData; + if ((this.options as MarkerSettings).useClusterMarkers) { + if (createdMarkers.length) { + this.markersCluster.addLayers(createdMarkers.map(marker => marker.leafletMarker)); + } + if (updatedMarkers.length) { + this.markersCluster.refreshClusters(updatedMarkers.map(marker => marker.leafletMarker)) + } + if (deletedMarkers.length) { + this.markersCluster.removeLayers(deletedMarkers.map(marker => marker.leafletMarker)); + } + } }); } @@ -350,22 +369,20 @@ export default abstract class LeafletMap { } private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, - updateBounds = true, callback?) { - const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker); + updateBounds = true, callback?): Marker { + const newMarker = new Marker(this, this.convertPosition(data), settings, data, dataSources, this.dragMarker); if (callback) newMarker.leafletMarker.on('click', () => { callback(data, true) }); if (this.bounds && updateBounds) this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng())); this.markers.set(key, newMarker); - if (this.options.useClusterMarkers) { - this.markersCluster.addLayer(newMarker.leafletMarker); - } - else { - this.map.addLayer(newMarker.leafletMarker); + if (!this.options.useClusterMarkers) { + this.map.addLayer(newMarker.leafletMarker); } + return newMarker; } - private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) { + private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings): Marker { const marker: Marker = this.markers.get(key); const location = this.convertPosition(data) if (!location.equals(marker.location)) { @@ -374,24 +391,21 @@ export default abstract class LeafletMap { if (settings.showTooltip) { marker.updateMarkerTooltip(data); } - if (settings.useClusterMarkers) { - this.markersCluster.refreshClusters() - } marker.setDataSources(data, dataSources); marker.updateMarkerIcon(settings); + return marker; } - deleteMarker(key: string) { - let marker = this.markers.get(key)?.leafletMarker; - if (marker) { - if (this.options.useClusterMarkers) { - this.markersCluster.removeLayer(marker); - } else { - this.map.removeLayer(marker); - } - this.markers.delete(key); - marker = null; - } + deleteMarker(key: string): Marker { + const marker = this.markers.get(key); + const leafletMarker = marker?.leafletMarker; + if (leafletMarker) { + if (!this.options.useClusterMarkers) { + this.map.removeLayer(leafletMarker); + } + this.markers.delete(key); + } + return marker; } updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts index b67925038c..0bb6c05aac 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts @@ -210,8 +210,9 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: match = buttonActionRegex.exec(template); } - const compiled = _.template(template); - res = compiled(data); + // const compiled = _.template(template); + // res = compiled(data); + res = template; } catch (ex) { console.log(ex, template) } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts index eec2e257ea..4bbfb22dd8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts @@ -19,6 +19,7 @@ import { FormattedData, MarkerSettings } from './map-models'; import { aspectCache, bindPopupActions, createTooltip, parseWithTranslation, safeExecute } from './maps-utils'; import tinycolor from 'tinycolor2'; import { isDefined } from '@core/utils'; +import LeafletMap from './leaflet-map'; export class Marker { leafletMarker: L.Marker; @@ -29,7 +30,7 @@ export class Marker { data: FormattedData; dataSources: FormattedData[]; - constructor(location: L.LatLngExpression, public settings: MarkerSettings, + constructor(private map: LeafletMap, location: L.LatLngExpression, public settings: MarkerSettings, data?: FormattedData, dataSources?, onDragendListener?) { this.setDataSources(data, dataSources); this.leafletMarker = L.marker(location, { @@ -158,24 +159,24 @@ export class Marker { } createDefaultMarkerIcon(color, onMarkerIconReady) { + if (!this.map.defaultMarkerIconInfo) { const icon = L.icon({ - iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color, - iconSize: [21, 34], - iconAnchor: [21 * this.markerOffset[0], 34 * this.markerOffset[1]], - popupAnchor: [0, -34], - shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow', - shadowSize: [40, 37], - shadowAnchor: [12, 35] + iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color, + iconSize: [21, 34], + iconAnchor: [21 * this.markerOffset[0], 34 * this.markerOffset[1]], + popupAnchor: [0, -34], + shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow', + shadowSize: [40, 37], + shadowAnchor: [12, 35] }); - const iconInfo = { - size: [21, 34], - icon + this.map.defaultMarkerIconInfo = { + size: [21, 34], + icon }; - onMarkerIconReady(iconInfo); + } + onMarkerIconReady(this.map.defaultMarkerIconInfo); } - - removeMarker() { /* this.map$.subscribe(map => this.leafletMarker.addTo(map))*/ From b2cb7c141c3e3fbbcdf49b9b037acdb64a4505c1 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 7 Aug 2020 14:41:49 +0300 Subject: [PATCH 02/18] Fix ui compilation --- ui-ngx/src/app/core/api/alarm-data.service.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/core/api/alarm-data.service.ts b/ui-ngx/src/app/core/api/alarm-data.service.ts index cdd9874098..23488c99c5 100644 --- a/ui-ngx/src/app/core/api/alarm-data.service.ts +++ b/ui-ngx/src/app/core/api/alarm-data.service.ts @@ -20,7 +20,6 @@ import { PageData } from '@shared/models/page/page-data'; import { AlarmData, AlarmDataPageLink, KeyFilter } from '@shared/models/query/query.models'; import { Injectable } from '@angular/core'; import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; -import { UtilsService } from '@core/services/utils.service'; import { AlarmDataSubscription, AlarmDataSubscriptionOptions, @@ -41,8 +40,7 @@ export interface AlarmDataListener { }) export class AlarmDataService { - constructor(private telemetryService: TelemetryWebsocketService, - private utils: UtilsService) {} + constructor(private telemetryService: TelemetryWebsocketService) {} public subscribeForAlarms(listener: AlarmDataListener, @@ -88,7 +86,7 @@ export class AlarmDataService { alarmDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters; } return new AlarmDataSubscription(alarmDataSubscriptionOptions, - listener, this.telemetryService, this.utils); + listener, this.telemetryService); } } From 6a4cd0d54a00e09c9b115377d3c5def1345d81d7 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Fri, 7 Aug 2020 18:26:06 +0300 Subject: [PATCH 03/18] Add map widget loading indicator --- .../components/widget/lib/maps/leaflet-map.ts | 22 +++++++++++++++- .../components/widget/lib/maps/map-widget2.ts | 2 ++ .../components/widget/lib/maps/maps-utils.ts | 25 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) 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 bdd3f8875d..7a2d091588 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 @@ -39,7 +39,7 @@ import { BehaviorSubject, Observable, of } from 'rxjs'; import { filter } from 'rxjs/operators'; import { Polyline } from './polyline'; import { Polygon } from './polygon'; -import { createTooltip, parseArray, safeExecute } from '@home/components/widget/lib/maps/maps-utils'; +import { createLoadingDiv, createTooltip, parseArray, safeExecute } from '@home/components/widget/lib/maps/maps-utils'; import { WidgetContext } from '@home/models/widget-component.models'; import { DatasourceData } from '@shared/models/widget.models'; import { deepClone, isDefinedAndNotNull } from '@core/utils'; @@ -60,6 +60,8 @@ export default abstract class LeafletMap { markersData: FormattedData[] = []; polygonsData: FormattedData[] = []; defaultMarkerIconInfo: { size: number[], icon: Icon }; + loadingDiv: JQuery; + loading = false; protected constructor(public ctx: WidgetContext, public $container: HTMLElement, @@ -169,6 +171,24 @@ export default abstract class LeafletMap { } } + public setLoading(loading: boolean) { + if (this.loading !== loading) { + this.loading = loading; + this.ready$.subscribe(() => { + if (this.loading) { + if (!this.loadingDiv) { + this.loadingDiv = createLoadingDiv(this.ctx.translate.instant('common.loading')); + } + this.$container.append(this.loadingDiv[0]); + } else { + if (this.loadingDiv) { + this.loadingDiv.remove(); + } + } + }); + } + } + public setMap(map: L.Map) { this.map = map; if (this.options.useDefaultCenterPosition) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts index fd06598fb3..7acc6e92e5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts @@ -85,6 +85,7 @@ export class MapWidgetController implements MapWidgetInterface { textSearch: null, dynamic: true }; + this.map.setLoading(true); this.ctx.defaultSubscription.subscribeAllForPaginatedData(this.pageLink, null); } @@ -279,6 +280,7 @@ export class MapWidgetController implements MapWidgetInterface { if (this.settings.draggableMarker) { this.map.setDataSources(formattedData); } + this.map.setLoading(false); } resize() { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts index 0bb6c05aac..ee785e6b04 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts @@ -322,3 +322,28 @@ export function calculateNewPointCoordinate(coordinate: number, imageSize: numbe } return pointCoordinate; } + +export function createLoadingDiv(loadingText: string): JQuery { + return $(` +
+ ${loadingText} +
+ `); +} From 13c21cca6515f35c46a07924f9d9179142f3a61e Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 7 Aug 2020 19:41:04 +0300 Subject: [PATCH 04/18] Optimize created marker label and tooltip --- ui-ngx/src/app/core/utils.ts | 18 ++-- .../components/widget/lib/maps/leaflet-map.ts | 8 +- .../components/widget/lib/maps/map-models.ts | 6 ++ .../components/widget/lib/maps/maps-utils.ts | 102 ++++++++++++++++-- .../components/widget/lib/maps/markers.ts | 26 ++++- 5 files changed, 141 insertions(+), 19 deletions(-) diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index dcfb90fbbc..e4823664bb 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -77,6 +77,10 @@ export function isUndefined(value: any): boolean { return typeof value === 'undefined'; } +export function isUndefinedOrNull(value: any): boolean { + return typeof value === 'undefined' || value === null; +} + export function isDefined(value: any): boolean { return typeof value !== 'undefined'; } @@ -452,7 +456,7 @@ export function insertVariable(pattern: string, name: string, value: any): strin const variable = match[0]; const variableName = match[1]; if (variableName === name) { - result = result.split(variable).join(value); + result = result.replace(variable, value); } match = varsRegex.exec(pattern); } @@ -469,17 +473,17 @@ export function createLabelFromDatasource(datasource: Datasource, pattern: strin const variable = match[0]; const variableName = match[1]; if (variableName === 'dsName') { - label = label.split(variable).join(datasource.name); + label = label.replace(variable, datasource.name); } else if (variableName === 'entityName') { - label = label.split(variable).join(datasource.entityName); + label = label.replace(variable, datasource.entityName); } else if (variableName === 'deviceName') { - label = label.split(variable).join(datasource.entityName); + label = label.replace(variable, datasource.entityName); } else if (variableName === 'entityLabel') { - label = label.split(variable).join(datasource.entityLabel || datasource.entityName); + label = label.replace(variable, datasource.entityLabel || datasource.entityName); } else if (variableName === 'aliasName') { - label = label.split(variable).join(datasource.aliasName); + label = label.replace(variable, datasource.aliasName); } else if (variableName === 'entityDescription') { - label = label.split(variable).join(datasource.entityDescription); + label = label.replace(variable, datasource.entityDescription); } match = varsRegex.exec(pattern); } 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 7a2d091588..33c36e3ce8 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 @@ -15,7 +15,8 @@ /// import L, { - FeatureGroup, Icon, + FeatureGroup, + Icon, LatLngBounds, LatLngTuple, markerClusterGroup, @@ -32,6 +33,7 @@ import { MarkerSettings, PolygonSettings, PolylineSettings, + ReplaceInfo, UnitedMapSettings } from './map-models'; import { Marker } from './markers'; @@ -62,6 +64,10 @@ export default abstract class LeafletMap { defaultMarkerIconInfo: { size: number[], icon: Icon }; loadingDiv: JQuery; loading = false; + replaceInfoLabelMarker: Array = []; + markerLabelText: string; + replaceInfoTooltipMarker: Array = []; + markerTooltipText: string; protected constructor(public ctx: WidgetContext, public $container: HTMLElement, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts index 32cf92eeae..0a41516b6e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts @@ -121,6 +121,12 @@ export interface FormattedData { [key: string]: any } +export interface ReplaceInfo { + variable: string; + valDec?: number; + dataKeyName: string +} + export type PolygonSettings = { showPolygon: boolean; polygonKeyName: string; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts index ee785e6b04..c484f0c336 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts @@ -15,13 +15,12 @@ /// import L from 'leaflet'; -import { FormattedData, MarkerSettings, PolygonSettings, PolylineSettings } from './map-models'; +import { FormattedData, MarkerSettings, PolygonSettings, PolylineSettings, ReplaceInfo } from './map-models'; import { Datasource, DatasourceData } from '@app/shared/models/widget.models'; import _ from 'lodash'; import { Observable, Observer, of } from 'rxjs'; import { map } from 'rxjs/operators'; -import { createLabelFromDatasource, hashCode, isNumber, isUndefined, padValue } from '@core/utils'; -import { Form } from '@angular/forms'; +import { createLabelFromDatasource, hashCode, isDefinedAndNotNull, isNumber, isUndefined, padValue } from '@core/utils'; export function createTooltip(target: L.Layer, settings: MarkerSettings | PolylineSettings | PolygonSettings, @@ -185,7 +184,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: } else { textValue = value; } - template = template.split(variable).join(textValue); + template = template.replace(variable, textValue); match = /\${([^}]*)}/g.exec(template); } @@ -198,7 +197,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: while (match !== null) { [actionTags, actionName, actionText] = match; action = createLinkElement(actionName, actionText); - template = template.split(actionTags).join(action); + template = template.replace(actionTags, action); match = linkActionRegex.exec(template); } @@ -206,7 +205,7 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: while (match !== null) { [actionTags, actionName, actionText] = match; action = createButtonElement(actionName, actionText); - template = template.split(actionTags).join(action); + template = template.replace(actionTags, action); match = buttonActionRegex.exec(template); } @@ -219,6 +218,94 @@ function parseTemplate(template: string, data: { $datasource?: Datasource, [key: return res; } +export function processPattern(template: string, data: { $datasource?: Datasource, [key: string]: any }): Array { + const replaceInfo = []; + try { + const reg = /\${([^}]*)}/g; + let match = reg.exec(template); + while (match !== null) { + const variableInfo: ReplaceInfo = { + dataKeyName: '', + valDec: 2, + variable: '' + }; + const variable = match[0]; + let label = match[1]; + let valDec = 2; + const splitValues = label.split(':'); + if (splitValues.length > 1) { + label = splitValues[0]; + valDec = parseFloat(splitValues[1]); + } + + variableInfo.variable = variable; + variableInfo.valDec = valDec; + + if (label.startsWith('#')) { + const keyIndexStr = label.substring(1); + const n = Math.floor(Number(keyIndexStr)); + if (String(n) === keyIndexStr && n >= 0) { + variableInfo.dataKeyName = data.$datasource.dataKeys[n].label; + } + } else { + variableInfo.dataKeyName = label; + } + replaceInfo.push(variableInfo); + + match = reg.exec(template); + } + } catch (ex) { + console.log(ex, template) + } + return replaceInfo; +} + +export function fillPattern(markerLabelText: string, replaceInfoLabelMarker: Array, data: FormattedData) { + let text = markerLabelText; + if (replaceInfoLabelMarker) { + for(const variableInfo of replaceInfoLabelMarker) { + let txtVal = ''; + if (variableInfo.dataKeyName && isDefinedAndNotNull(data[variableInfo.dataKeyName])) { + const varData = data[variableInfo.dataKeyName]; + if (isNumber(varData)) { + txtVal = padValue(varData, variableInfo.valDec); + } else { + txtVal = varData; + } + } + text = text.replace(variableInfo.variable, txtVal); + } + } + return text; +} + +function prepareProcessPattern(template: string, translateFn?: TranslateFunc): string { + if (translateFn) { + template = translateFn(template); + } + let actionTags: string; + let actionText: string; + let actionName: string; + let action: string; + + let match = linkActionRegex.exec(template); + while (match !== null) { + [actionTags, actionName, actionText] = match; + action = createLinkElement(actionName, actionText); + template = template.replace(actionTags, action); + match = linkActionRegex.exec(template); + } + + match = buttonActionRegex.exec(template); + while (match !== null) { + [actionTags, actionName, actionText] = match; + action = createButtonElement(actionName, actionText); + template = template.replace(actionTags, action); + match = buttonActionRegex.exec(template); + } + return template; +} + export const parseWithTranslation = { translateFn: null, @@ -233,6 +320,9 @@ export const parseWithTranslation = { parseTemplate(template: string, data: object, forceTranslate = false): string { return parseTemplate(forceTranslate ? this.translate(template) : template, data, this.translate.bind(this)); }, + prepareProcessPattern(template: string, forceTranslate = false): string { + return prepareProcessPattern(forceTranslate ? this.translate(template) : template, this.translate.bind(this)); + }, setTranslate(translateFn: TranslateFunc) { this.translateFn = translateFn; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts index 4bbfb22dd8..cd254ca406 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts @@ -16,7 +16,15 @@ import L, { LeafletMouseEvent } from 'leaflet'; import { FormattedData, MarkerSettings } from './map-models'; -import { aspectCache, bindPopupActions, createTooltip, parseWithTranslation, safeExecute } from './maps-utils'; +import { + aspectCache, + bindPopupActions, + createTooltip, + fillPattern, + parseWithTranslation, + processPattern, + safeExecute +} from './maps-utils'; import tinycolor from 'tinycolor2'; import { isDefined } from '@core/utils'; import LeafletMap from './leaflet-map'; @@ -74,9 +82,13 @@ export class Marker { } updateMarkerTooltip(data: FormattedData) { + if(!this.map.markerTooltipText || this.settings.useTooltipFunction) { const pattern = this.settings.useTooltipFunction ? - safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern; - this.tooltip.setContent(parseWithTranslation.parseTemplate(pattern, data, true)); + safeExecute(this.settings.tooltipFunction, [this.data, this.dataSources, this.data.dsIndex]) : this.settings.tooltipPattern; + this.map.markerTooltipText = parseWithTranslation.prepareProcessPattern(pattern, true); + this.map.replaceInfoTooltipMarker = processPattern(this.map.markerTooltipText, data); + } + this.tooltip.setContent(fillPattern(this.map.markerTooltipText, this.map.replaceInfoTooltipMarker, data)); if (this.tooltip.isOpen() && this.tooltip.getElement()) { bindPopupActions(this.tooltip, this.settings, data.$datasource); } @@ -89,9 +101,13 @@ export class Marker { updateMarkerLabel(settings: MarkerSettings) { this.leafletMarker.unbindTooltip(); if (settings.showLabel) { - const pattern = settings.useLabelFunction ? + if(!this.map.markerLabelText || settings.useLabelFunction) { + const pattern = settings.useLabelFunction ? safeExecute(settings.labelFunction, [this.data, this.dataSources, this.data.dsIndex]) : settings.label; - settings.labelText = parseWithTranslation.parseTemplate(pattern, this.data, true); + this.map.markerLabelText = parseWithTranslation.prepareProcessPattern(pattern, true); + this.map.replaceInfoLabelMarker = processPattern(this.map.markerLabelText, this.data); + } + settings.labelText = fillPattern(this.map.markerLabelText, this.map.replaceInfoLabelMarker, this.data); this.leafletMarker.bindTooltip(`
${settings.labelText}
`, { className: 'tb-marker-label', permanent: true, direction: 'top', offset: this.tooltipOffset }); } From c4ae27795ab01d80b92ffc1f0ec5740775508759 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 7 Aug 2020 19:44:30 +0300 Subject: [PATCH 05/18] Add use createdLabelForDatasource --- .../app/modules/home/components/widget/lib/maps/maps-utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts index c484f0c336..a94144c061 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts @@ -261,7 +261,7 @@ export function processPattern(template: string, data: { $datasource?: Datasourc } export function fillPattern(markerLabelText: string, replaceInfoLabelMarker: Array, data: FormattedData) { - let text = markerLabelText; + let text = createLabelFromDatasource(data.$datasource, markerLabelText); if (replaceInfoLabelMarker) { for(const variableInfo of replaceInfoLabelMarker) { let txtVal = ''; From ec7ea7042a46e5cbbf460ae25fd9c65b5585bb7b Mon Sep 17 00:00:00 2001 From: Vladyslav Prykhodko Date: Mon, 10 Aug 2020 00:15:09 +0300 Subject: [PATCH 06/18] Fixed loading of data used by post-processing function in key settings for map widgets --- ui-ngx/package-lock.json | 46 ++++++++++++++++--- ui-ngx/package.json | 2 +- ui-ngx/src/app/core/utils.ts | 4 ++ .../components/widget/lib/maps/leaflet-map.ts | 6 +-- .../components/widget/lib/maps/maps-utils.ts | 4 +- .../widget/lib/maps/providers/image-map.ts | 4 +- 6 files changed, 51 insertions(+), 15 deletions(-) diff --git a/ui-ngx/package-lock.json b/ui-ngx/package-lock.json index 0478e37ac8..443b1e5999 100644 --- a/ui-ngx/package-lock.json +++ b/ui-ngx/package-lock.json @@ -2127,9 +2127,9 @@ } }, "@types/leaflet": { - "version": "1.5.12", - "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.5.12.tgz", - "integrity": "sha512-61HRMIng+bWvnnAIqUWLBlrd/TQZc4gU+gN1JL4K47EDtwIrcMEhWgi7PdcpbG1YmpH4F0EfOimkvV82gJIl9w==", + "version": "1.5.17", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.5.17.tgz", + "integrity": "sha512-2XYq9k6kNjhNI7PaTz8Rdxcc8Vzwu97OaS9CtcrTxnTSxFUGwjlGjTDvhTLJU+JRSfZ4lBwGcl0SjZHALdVr6g==", "dev": true, "requires": { "@types/geojson": "*" @@ -3100,6 +3100,16 @@ "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow==", "dev": true }, + "bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "dev": true, + "optional": true, + "requires": { + "file-uri-to-path": "1.0.0" + } + }, "blob": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/blob/-/blob-0.0.5.tgz", @@ -5843,6 +5853,13 @@ "tslib": "^1.9.0" } }, + "file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "dev": true, + "optional": true + }, "fileset": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", @@ -8938,6 +8955,13 @@ "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "dev": true }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", + "dev": true, + "optional": true + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -8997,10 +9021,10 @@ "integrity": "sha512-4O3GWAYJaauMCILm07weko2rHA8a4kjn7+8Lg4s1d7SxwS/3IpkVD/GljbRrIJ1c1W/XGJ3GbuK7RyYZEJChhw==" }, "ngx-flowchart": { - "version": "git://github.com/thingsboard/ngx-flowchart.git#7a02f4748b5e7821a883c903107af5f20415d026", + "version": "git://github.com/thingsboard/ngx-flowchart.git#97a77477ca8579becf0e3a07866046b4536fe30a", "from": "git://github.com/thingsboard/ngx-flowchart.git#master", "requires": { - "tslib": "^1.13.0" + "tslib": "^1.10.0" }, "dependencies": { "tslib": { @@ -14065,7 +14089,11 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, - "optional": true + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } }, "is-binary-path": { "version": "1.0.1", @@ -14500,7 +14528,11 @@ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "dev": true, - "optional": true + "optional": true, + "requires": { + "bindings": "^1.5.0", + "nan": "^2.12.1" + } }, "is-absolute-url": { "version": "3.0.3", diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 9f6b79d24b..8a463512e1 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -106,7 +106,7 @@ "@types/js-beautify": "^1.8.2", "@types/jstree": "^3.3.40", "@types/jszip": "^3.4.1", - "@types/leaflet": "^1.5.12", + "@types/leaflet": "^1.5.17", "@types/leaflet-markercluster": "^1.0.3", "@types/leaflet-polylinedecorator": "^1.6.0", "@types/lodash": "^4.14.151", diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index e4823664bb..f8ccb6ae61 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -89,6 +89,10 @@ export function isDefinedAndNotNull(value: any): boolean { return typeof value !== 'undefined' && value !== null; } +export function isDefinedAndNotEmptyStr(value: any): boolean { + return typeof value !== 'undefined' && value !== ''; +} + export function isFunction(value: any): boolean { return typeof value === 'function'; } 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 33c36e3ce8..ddb84fc77b 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 @@ -44,7 +44,7 @@ import { Polygon } from './polygon'; import { createLoadingDiv, createTooltip, parseArray, safeExecute } from '@home/components/widget/lib/maps/maps-utils'; import { WidgetContext } from '@home/models/widget-component.models'; import { DatasourceData } from '@shared/models/widget.models'; -import { deepClone, isDefinedAndNotNull } from '@core/utils'; +import { deepClone, isDefinedAndNotEmptyStr } from '@core/utils'; export default abstract class LeafletMap { @@ -276,7 +276,7 @@ export default abstract class LeafletMap { if (!expression) return null; const lat = expression[this.options.latKeyName]; const lng = expression[this.options.lngKeyName]; - if (!isDefinedAndNotNull(lat) || isNaN(lat) || !isDefinedAndNotNull(lng) || isNaN(lng)) { + if (!isDefinedAndNotEmptyStr(lat) || isNaN(lat) || !isDefinedAndNotEmptyStr(lng) || isNaN(lng)) { return null; } return L.latLng(lat, lng) as L.LatLng; @@ -524,7 +524,7 @@ export default abstract class LeafletMap { const keys: string[] = []; this.polygonsData = deepClone(polyData); polyData.forEach((data: FormattedData) => { - if (data && data.hasOwnProperty(this.options.polygonKeyName) && data[this.options.polygonKeyName] !== null) { + if (data && isDefinedAndNotEmptyStr(data[this.options.polygonKeyName])) { if (typeof (data[this.options.polygonKeyName]) === 'string') { data[this.options.polygonKeyName] = JSON.parse(data[this.options.polygonKeyName]); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts index a94144c061..87b99d3499 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts @@ -341,8 +341,8 @@ export function parseData(input: DatasourceData[]): FormattedData[] { }; entityArray.filter(el => el.data.length).forEach(el => { const indexDate = el?.data?.length ? el.data.length - 1 : 0; - obj[el?.dataKey?.label] = el?.data[indexDate][0] ? el?.data[indexDate][1] : null; - obj[el?.dataKey?.label + '|ts'] = el?.data[indexDate][0] || null; + obj[el?.dataKey?.label] = el?.data[indexDate][1]; + obj[el?.dataKey?.label + '|ts'] = el?.data[indexDate][0]; if (el?.dataKey?.label === 'type') { obj.deviceType = el?.data[indexDate][1]; } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts index b8c0a9c82e..82f8120bac 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts @@ -24,7 +24,7 @@ import { WidgetContext } from '@home/models/widget-component.models'; import { DataSet, DatasourceType, widgetType } from '@shared/models/widget.models'; import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; import { WidgetSubscriptionOptions } from '@core/api/widget-api.models'; -import { isDefinedAndNotNull } from '@core/utils'; +import { isDefinedAndNotEmptyStr } from '@core/utils'; const maxZoom = 4;// ? @@ -212,7 +212,7 @@ export class ImageMap extends LeafletMap { convertPosition(expression): L.LatLng { const xPos = expression[this.options.xPosKeyName]; const yPos = expression[this.options.yPosKeyName]; - if (!isDefinedAndNotNull(xPos) || isNaN(xPos) || !isDefinedAndNotNull(yPos) || isNaN(yPos)) { + if (!isDefinedAndNotEmptyStr(xPos) || isNaN(xPos) || !isDefinedAndNotEmptyStr(yPos) || isNaN(yPos)) { return null; } Object.assign(expression, this.posFunction(xPos, yPos)); From cffe2135f2149cb748ca5e355f40e146640b27c5 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 10 Aug 2020 10:29:55 +0300 Subject: [PATCH 07/18] Fix updated bounds for marker cluster mode --- .../components/widget/lib/maps/leaflet-map.ts | 34 ++++++++++++------- 1 file changed, 21 insertions(+), 13 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 33c36e3ce8..33c0c36c3b 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 @@ -320,9 +320,13 @@ export default abstract class LeafletMap { bounds.extend(polygon.leafletPoly.getBounds()); }); } - this.markers.forEach((marker) => { - bounds.extend(marker.leafletMarker.getLatLng()); - }); + if ((this.options as MarkerSettings).useClusterMarkers) { + bounds.extend(this.markersCluster.getBounds()); + } else { + this.markers.forEach((marker) => { + bounds.extend(marker.leafletMarker.getLatLng()); + }); + } const mapBounds = this.map.getBounds(); if (bounds.isValid() && (!this.bounds || !mapBounds.contains(bounds))) { @@ -396,16 +400,20 @@ export default abstract class LeafletMap { private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, updateBounds = true, callback?): Marker { - const newMarker = new Marker(this, this.convertPosition(data), settings, data, dataSources, this.dragMarker); - if (callback) - newMarker.leafletMarker.on('click', () => { callback(data, true) }); - if (this.bounds && updateBounds) - this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng())); - this.markers.set(key, newMarker); - if (!this.options.useClusterMarkers) { - this.map.addLayer(newMarker.leafletMarker); - } - return newMarker; + const newMarker = new Marker(this, this.convertPosition(data), settings, data, dataSources, this.dragMarker); + if (callback) { + newMarker.leafletMarker.on('click', () => { + callback(data, true) + }); + } + if (this.bounds && updateBounds && !(this.options as MarkerSettings).useClusterMarkers) { + this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng())); + } + this.markers.set(key, newMarker); + if (!this.options.useClusterMarkers) { + this.map.addLayer(newMarker.leafletMarker); + } + return newMarker; } private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings): Marker { From 85a1448b43908579257e9a7ba4724d11e9cd1531 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 10 Aug 2020 12:27:28 +0300 Subject: [PATCH 08/18] Correction bulk provision without telemetry --- ui-ngx/src/app/core/http/entity.service.ts | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 1773582ea7..7034029e93 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -937,16 +937,20 @@ export class EntityService { ); observables.push(observable); } - return forkJoin(observables).pipe( - map((response) => { - const hasError = response.filter((status) => status === 'error').length > 0; - if (hasError) { - throw Error(); - } else { - return response; - } - }) - ); + if (observables.length) { + return forkJoin(observables).pipe( + map((response) => { + const hasError = response.filter((status) => status === 'error').length > 0; + if (hasError) { + throw Error(); + } else { + return response; + } + }) + ); + } else { + return of(null); + } } private getStateEntityInfo(filter: EntityAliasFilter, stateParams: StateParams): {entityId: EntityId} { From 5cc7bbe635bc63012adbaae3de3e227f5be007f2 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 10 Aug 2020 12:53:33 +0300 Subject: [PATCH 09/18] Added ADDITIONAL INFO --- .../query/DefaultEntityQueryRepository.java | 20 +++++++++++++++-- .../dao/sql/query/EntityKeyMapping.java | 22 ++++++++++++------- 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index 4fe58f7da4..e45bf4e488 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -189,6 +189,22 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { " WHEN entity.entity_type = 'ENTITY_VIEW'" + " THEN (select name from entity_view where id = entity_id)" + " END as label"; + private static final String SELECT_ADDITIONAL_INFO = " CASE" + + " WHEN entity.entity_type = 'TENANT'" + + " THEN (select additional_info from tenant where id = entity_id)" + + " WHEN entity.entity_type = 'CUSTOMER' " + + " THEN (select additional_info from customer where id = entity_id)" + + " WHEN entity.entity_type = 'USER'" + + " THEN (select additional_info from tb_user where id = entity_id)" + + " WHEN entity.entity_type = 'DASHBOARD'" + + " THEN (select '' from dashboard where id = entity_id)" + + " WHEN entity.entity_type = 'ASSET'" + + " THEN (select additional_info from asset where id = entity_id)" + + " WHEN entity.entity_type = 'DEVICE'" + + " THEN (select additional_info from device where id = entity_id)" + + " WHEN entity.entity_type = 'ENTITY_VIEW'" + + " THEN (select additional_info from entity_view where id = entity_id)" + + " END as label"; static { entityTableMap.put(EntityType.ASSET, "asset"); @@ -470,7 +486,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { + SELECT_TYPE + ", " + SELECT_NAME + ", " + SELECT_LABEL + ", " + SELECT_FIRST_NAME + ", " + SELECT_LAST_NAME + ", " + SELECT_EMAIL + ", " + SELECT_REGION + ", " + SELECT_TITLE + ", " + SELECT_COUNTRY + ", " + SELECT_STATE + ", " + SELECT_CITY + ", " + - SELECT_ADDRESS + ", " + SELECT_ADDRESS_2 + ", " + SELECT_ZIP + ", " + SELECT_PHONE + + SELECT_ADDRESS + ", " + SELECT_ADDRESS_2 + ", " + SELECT_ZIP + ", " + SELECT_PHONE + ", " + SELECT_ADDITIONAL_INFO + ", entity.entity_type as entity_type"; String from = getQueryTemplate(entityFilter.getDirection()); ctx.addUuidParameter("relation_root_id", rootId.getId()); @@ -557,7 +573,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { if (!StringUtils.isEmpty(searchText) && !selectionMapping.isEmpty()) { String lowerSearchText = "%" + searchText.toLowerCase() + "%"; ctx.addStringParameter("lowerSearchTextParam", lowerSearchText); - List searchAliases = selectionMapping.stream().map(EntityKeyMapping::getValueAlias).collect(Collectors.toList()); + List searchAliases = selectionMapping.stream().filter(EntityKeyMapping::isSearchable).map(EntityKeyMapping::getValueAlias).collect(Collectors.toList()); String searchAliasesExpression; if (searchAliases.size() > 1) { searchAliasesExpression = "CONCAT(" + String.join(" , ", searchAliases) + ")"; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 221ed472e7..e11e7859cc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -71,15 +71,17 @@ public class EntityKeyMapping { private static final String ADDRESS_2 = "address2"; private static final String ZIP = "zip"; private static final String PHONE = "phone"; + private static final String ADDITIONAL_INFO = "additionalInfo"; - public static final List typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE); - public static final List commonEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME); + public static final List typedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, ADDITIONAL_INFO); + public static final List widgetEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME); + public static final List commonEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, ADDITIONAL_INFO); public static final List dashboardEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE); - public static final List labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL); - public static final List contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE); + public static final List labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO); + public static final List contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO); public static final Set commonEntityFieldsSet = new HashSet<>(commonEntityFields); - public static final Set relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE)); + public static final Set relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO)); static { allowedEntityFieldMap.put(EntityType.DEVICE, new HashSet<>(labeledEntityFields)); @@ -90,13 +92,13 @@ public class EntityKeyMapping { allowedEntityFieldMap.get(EntityType.TENANT).add(REGION); allowedEntityFieldMap.put(EntityType.CUSTOMER, new HashSet<>(contactBasedEntityFields)); - allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL))); + allowedEntityFieldMap.put(EntityType.USER, new HashSet<>(Arrays.asList(CREATED_TIME, FIRST_NAME, LAST_NAME, EMAIL, ADDITIONAL_INFO))); allowedEntityFieldMap.put(EntityType.DASHBOARD, new HashSet<>(dashboardEntityFields)); allowedEntityFieldMap.put(EntityType.RULE_CHAIN, new HashSet<>(commonEntityFields)); allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields)); - allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(commonEntityFields)); - allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(commonEntityFields)); + allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(widgetEntityFields)); + allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(widgetEntityFields)); entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY); entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY); @@ -115,6 +117,7 @@ public class EntityKeyMapping { entityFieldColumnMap.put(ADDRESS_2, ModelConstants.ADDRESS2_PROPERTY); entityFieldColumnMap.put(ZIP, ModelConstants.ZIP_PROPERTY); entityFieldColumnMap.put(PHONE, ModelConstants.PHONE_PROPERTY); + entityFieldColumnMap.put(ADDITIONAL_INFO, ModelConstants.ADDITIONAL_INFO_PROPERTY); Map contactBasedAliases = new HashMap<>(); contactBasedAliases.put(NAME, TITLE); @@ -140,6 +143,7 @@ public class EntityKeyMapping { private String alias; private boolean isLatest; private boolean isSelection; + private boolean isSearchable; private boolean isSortOrder; private boolean ignore = false; private List keyFilters; @@ -304,6 +308,7 @@ public class EntityKeyMapping { EntityKeyMapping mapping = new EntityKeyMapping(); mapping.setLatest(false); mapping.setSelection(true); + mapping.setSearchable(!key.getKey().equals(ADDITIONAL_INFO)); mapping.setEntityKey(key); return mapping; } @@ -312,6 +317,7 @@ public class EntityKeyMapping { key -> { EntityKeyMapping mapping = new EntityKeyMapping(); mapping.setLatest(true); + mapping.setSearchable(true); mapping.setSelection(true); mapping.setEntityKey(key); return mapping; From cc5b2cc4a62bb66ab4a0f35195b3def20d20e893 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 10 Aug 2020 13:57:12 +0300 Subject: [PATCH 10/18] Log SQL Queries based on execution time threshold --- .../src/main/resources/thingsboard.yml | 3 +- .../query/DefaultAlarmQueryRepository.java | 30 +++++++++---- .../query/DefaultEntityQueryRepository.java | 45 +++++++++++-------- .../sql/query/DefaultQueryLogComponent.java | 40 +++++++++++++++++ .../dao/sql/query/QueryLogComponent.java | 21 +++++++++ 5 files changed, 110 insertions(+), 29 deletions(-) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/query/QueryLogComponent.java diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index cafed53f90..54a3dac870 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -273,7 +273,8 @@ sql: # Specify whether to remove null characters from strValue of attributes and timeseries before insert remove_null_chars: "${SQL_REMOVE_NULL_CHARS:true}" # Specify whether to log database queries and their parameters generated by entity query repository - log_entity_queries: "${SQL_LOG_ENTITY_QUERIES:false}" + log_queries: "${SQL_LOG_QUERIES:false}" + log_queries_threshold: "${SQL_LOG_QUERIES_THRESHOLD:5000}" postgres: # Specify partitioning size for timestamp key-value storage. Example: DAYS, MONTHS, YEARS, INDEFINITE. ts_key_value_partitioning: "${SQL_POSTGRES_TS_KV_PARTITIONING:MONTHS}" diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index 29b070c9e9..3ad734deb1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -43,6 +43,7 @@ import org.thingsboard.server.dao.model.ModelConstants; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -114,12 +115,12 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { protected final NamedParameterJdbcTemplate jdbcTemplate; private final TransactionTemplate transactionTemplate; - @Value("${sql.log_entity_queries:false}") - private boolean logSqlQueries; + private final DefaultQueryLogComponent queryLog; - public DefaultAlarmQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate) { + public DefaultAlarmQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate, DefaultQueryLogComponent queryLog) { this.jdbcTemplate = jdbcTemplate; this.transactionTemplate = transactionTemplate; + this.queryLog = queryLog; } @Override @@ -230,8 +231,17 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { if (!textSearchQuery.isEmpty()) { mainQuery = String.format("select * from (%s) a WHERE %s", mainQuery, textSearchQuery); } - String countQuery = mainQuery; - int totalElements = jdbcTemplate.queryForObject(String.format("select count(*) from (%s) result", countQuery), ctx, Integer.class); + String countQuery = String.format("select count(*) from (%s) result", mainQuery); + long queryTs = System.currentTimeMillis(); + int totalElements; + try { + totalElements = jdbcTemplate.queryForObject(countQuery, ctx, Integer.class); + } finally { + queryLog.logQuery(ctx, countQuery, System.currentTimeMillis() - queryTs); + } + if (totalElements == 0) { + return AlarmDataAdapter.createAlarmData(pageLink, Collections.emptyList(), totalElements, orderedEntityIds); + } String dataQuery = mainQuery + sortPart; @@ -239,10 +249,12 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { if (pageLink.getPageSize() > 0) { dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex); } - List> rows = jdbcTemplate.queryForList(dataQuery, ctx); - if (logSqlQueries) { - log.info("QUERY: {}", dataQuery); - Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {}->{}", param, ctx.getValue(param))); + queryTs = System.currentTimeMillis(); + List> rows; + try { + rows = jdbcTemplate.queryForList(dataQuery, ctx); + } finally { + queryLog.logQuery(ctx, dataQuery, System.currentTimeMillis() - queryTs); } return AlarmDataAdapter.createAlarmData(pageLink, rows, totalElements, orderedEntityIds); }); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index e45bf4e488..b3e2361da2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -17,12 +17,8 @@ package org.thingsboard.server.dao.sql.query; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.stereotype.Repository; -import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.TransactionCallback; import org.springframework.transaction.support.TransactionTemplate; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.CustomerId; @@ -237,13 +233,12 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { private final NamedParameterJdbcTemplate jdbcTemplate; private final TransactionTemplate transactionTemplate; + private final DefaultQueryLogComponent queryLog; - @Value("${sql.log_entity_queries:false}") - private boolean logSqlQueries; - - public DefaultEntityQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate) { + public DefaultEntityQueryRepository(NamedParameterJdbcTemplate jdbcTemplate, TransactionTemplate transactionTemplate, DefaultQueryLogComponent queryLog) { this.jdbcTemplate = jdbcTemplate; this.transactionTemplate = transactionTemplate; + this.queryLog = queryLog; } @Override @@ -254,11 +249,14 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { ctx.append(addEntityTableQuery(ctx, query.getEntityFilter())); ctx.append(" e where "); ctx.append(buildEntityWhere(ctx, query.getEntityFilter(), Collections.emptyList())); - if (logSqlQueries) { - log.info("QUERY: {}", ctx.getQuery()); - Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {}->{}", param, ctx.getValue(param))); - } - return transactionTemplate.execute(status -> jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class)); + return transactionTemplate.execute(status -> { + long startTs = System.currentTimeMillis(); + try { + return jdbcTemplate.queryForObject(ctx.getQuery(), ctx, Long.class); + } finally { + queryLog.logQuery(ctx, ctx.getQuery(), System.currentTimeMillis() - startTs); + } + }); } @Override @@ -329,7 +327,14 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { fromClauseCount = fromClauseData; } String countQuery = String.format("select count(id) %s", fromClauseCount); - int totalElements = jdbcTemplate.queryForObject(countQuery, ctx, Integer.class); + + long startTs = System.currentTimeMillis(); + int totalElements; + try { + totalElements = jdbcTemplate.queryForObject(countQuery, ctx, Integer.class); + } finally { + queryLog.logQuery(ctx, countQuery, System.currentTimeMillis() - startTs); + } if (totalElements == 0) { return new PageData<>(); @@ -354,11 +359,13 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { if (pageLink.getPageSize() > 0) { dataQuery = String.format("%s limit %s offset %s", dataQuery, pageLink.getPageSize(), startIndex); } - if (logSqlQueries) { - log.info("QUERY: {}", dataQuery); - Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {}->{}", param, ctx.getValue(param))); + startTs = System.currentTimeMillis(); + List> rows; + try { + rows = jdbcTemplate.queryForList(dataQuery, ctx); + } finally { + queryLog.logQuery(ctx, countQuery, System.currentTimeMillis() - startTs); } - List> rows = jdbcTemplate.queryForList(dataQuery, ctx); return EntityDataAdapter.createEntityData(pageLink, selectionMapping, rows, totalElements); }); } @@ -486,7 +493,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { + SELECT_TYPE + ", " + SELECT_NAME + ", " + SELECT_LABEL + ", " + SELECT_FIRST_NAME + ", " + SELECT_LAST_NAME + ", " + SELECT_EMAIL + ", " + SELECT_REGION + ", " + SELECT_TITLE + ", " + SELECT_COUNTRY + ", " + SELECT_STATE + ", " + SELECT_CITY + ", " + - SELECT_ADDRESS + ", " + SELECT_ADDRESS_2 + ", " + SELECT_ZIP + ", " + SELECT_PHONE + ", " + SELECT_ADDITIONAL_INFO + + SELECT_ADDRESS + ", " + SELECT_ADDRESS_2 + ", " + SELECT_ZIP + ", " + SELECT_PHONE + ", " + SELECT_ADDITIONAL_INFO + ", entity.entity_type as entity_type"; String from = getQueryTemplate(entityFilter.getDirection()); ctx.addUuidParameter("relation_root_id", rootId.getId()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java new file mode 100644 index 0000000000..7cece0cf3a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultQueryLogComponent.java @@ -0,0 +1,40 @@ +/** + * 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.dao.sql.query; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import java.util.Arrays; + +@Component +@Slf4j +public class DefaultQueryLogComponent implements QueryLogComponent { + + @Value("${sql.log_queries:false}") + private boolean logSqlQueries; + @Value("${sql.log_queries_threshold:5000}") + private long logQueriesThreshold; + + @Override + public void logQuery(QueryContext ctx, String query, long duration) { + if (logSqlQueries && duration > logQueriesThreshold) { + log.info("QUERY: {} took {}ms", query, duration); + Arrays.asList(ctx.getParameterNames()).forEach(param -> log.info("QUERY PARAM: {} -> {}", param, ctx.getValue(param))); + } + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/QueryLogComponent.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/QueryLogComponent.java new file mode 100644 index 0000000000..a775626766 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/QueryLogComponent.java @@ -0,0 +1,21 @@ +/** + * 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.dao.sql.query; + +public interface QueryLogComponent { + + void logQuery(QueryContext ctx, String query, long duration); +} From c1b53afd8a965617e0443f245d9e03b5039ccfde Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 10 Aug 2020 14:34:05 +0300 Subject: [PATCH 11/18] Add entity description to datasource. Fix map markers color. --- .../app/core/api/entity-data-subscription.ts | 6 ++ ui-ngx/src/app/core/api/widget-api.models.ts | 1 + .../src/app/core/api/widget-subscription.ts | 17 +++++- .../components/widget/lib/maps/leaflet-map.ts | 4 +- .../components/widget/lib/maps/map-models.ts | 2 + .../components/widget/lib/maps/markers.ts | 55 ++++++++++++------- .../lib/timeseries-table-widget.component.ts | 3 +- .../app/shared/models/query/query.models.ts | 17 +++++- 8 files changed, 81 insertions(+), 24 deletions(-) diff --git a/ui-ngx/src/app/core/api/entity-data-subscription.ts b/ui-ngx/src/app/core/api/entity-data-subscription.ts index c1b01daba6..6d2f764d95 100644 --- a/ui-ngx/src/app/core/api/entity-data-subscription.ts +++ b/ui-ngx/src/app/core/api/entity-data-subscription.ts @@ -193,6 +193,12 @@ export class EntityDataSubscription { key: 'label' }); } + if (!entityFields.find(key => key.key === 'additionalInfo')) { + entityFields.push({ + type: EntityKeyType.ENTITY_FIELD, + key: 'additionalInfo' + }); + } this.attrFields = this.entityDataSubscriptionOptions.dataKeys.filter(dataKey => dataKey.type === DataKeyType.attribute).map( dataKey => ({ type: EntityKeyType.ATTRIBUTE, key: dataKey.name }) diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index fe7bb82044..e4b9deae89 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -242,6 +242,7 @@ export interface SubscriptionEntityInfo { entityId: EntityId; entityName: string; entityLabel: string; + entityDescription: string; } export interface IWidgetSubscription { diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index d664e06a5e..0eb71c7e90 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -455,6 +455,7 @@ export class WidgetSubscription implements IWidgetSubscription { let entityId: EntityId; let entityName: string; let entityLabel: string; + let entityDescription: string; if (this.type === widgetType.rpc) { if (this.targetDeviceId) { entityId = { @@ -474,6 +475,18 @@ export class WidgetSubscription implements IWidgetSubscription { if (labelValue) { entityLabel = labelValue.value; } + const additionalInfoValue = entityFields.additionalInfo; + if (additionalInfoValue) { + const additionalInfo = additionalInfoValue.value; + if (additionalInfo && additionalInfo.length) { + try { + const additionalInfoJson = JSON.parse(additionalInfo); + if (additionalInfoJson && additionalInfoJson.description) { + entityDescription = additionalInfoJson.description; + } + } catch (e) {} + } + } } } } else { @@ -485,6 +498,7 @@ export class WidgetSubscription implements IWidgetSubscription { }; entityName = datasource.entityName; entityLabel = datasource.entityLabel; + entityDescription = datasource.entityDescription; break; } } @@ -493,7 +507,8 @@ export class WidgetSubscription implements IWidgetSubscription { return { entityId, entityName, - entityLabel + entityLabel, + entityDescription }; } else { return null; 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 33c36e3ce8..97bbd6e18c 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 @@ -23,11 +23,12 @@ import L, { MarkerClusterGroup, MarkerClusterGroupOptions } from 'leaflet'; - +import tinycolor from 'tinycolor2'; import 'leaflet-providers'; import 'leaflet.markercluster/dist/leaflet.markercluster'; import { + defaultSettings, FormattedData, MapSettings, MarkerSettings, @@ -76,6 +77,7 @@ export default abstract class LeafletMap { } public initSettings(options: MapSettings) { + this.options.tinyColor = tinycolor(this.options.color || defaultSettings.color); const { initCallback, disableScrollZooming, useClusterMarkers, diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts index 0a41516b6e..d0da933e8a 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts @@ -24,6 +24,7 @@ import { googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema } from './schemes'; import { EntityType } from '@shared/models/entity-type.models'; +import tinycolor from 'tinycolor2'; export const DEFAULT_MAP_PAGE_SIZE = 16384; @@ -93,6 +94,7 @@ export type MarkerSettings = { useTooltipFunction: boolean; useColorFunction: boolean; color?: string; + tinyColor?: tinycolor.Instance; autocloseTooltip: boolean; showTooltipAction: string; useClusterMarkers: boolean; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts index cd254ca406..efad5931c9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import L, { LeafletMouseEvent } from 'leaflet'; +import L, { Icon, LeafletMouseEvent } from 'leaflet'; import { FormattedData, MarkerSettings } from './map-models'; import { aspectCache, @@ -26,7 +26,7 @@ import { safeExecute } from './maps-utils'; import tinycolor from 'tinycolor2'; -import { isDefined } from '@core/utils'; +import { isDefined, isDefinedAndNotNull } from '@core/utils'; import LeafletMap from './leaflet-map'; export class Marker { @@ -138,8 +138,14 @@ export class Marker { const currentImage = this.settings.useMarkerImageFunction ? safeExecute(this.settings.markerImageFunction, [this.data, this.settings.markerImages, this.dataSources, this.data.dsIndex]) : this.settings.currentImage; - const currentColor = tinycolor(this.settings.useColorFunction ? safeExecute(this.settings.colorFunction, - [this.data, this.dataSources, this.data.dsIndex]) : this.settings.color).toHex(); + let currentColor = this.settings.tinyColor; + if (this.settings.useColorFunction) { + const functionColor = safeExecute(this.settings.colorFunction, + [this.data, this.dataSources, this.data.dsIndex]); + if (isDefinedAndNotNull(functionColor)) { + currentColor = tinycolor(functionColor); + } + } if (currentImage && currentImage.url) { aspectCache(currentImage.url).subscribe( (aspect) => { @@ -174,23 +180,32 @@ export class Marker { } } - createDefaultMarkerIcon(color, onMarkerIconReady) { - if (!this.map.defaultMarkerIconInfo) { - const icon = L.icon({ - iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color, - iconSize: [21, 34], - iconAnchor: [21 * this.markerOffset[0], 34 * this.markerOffset[1]], - popupAnchor: [0, -34], - shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow', - shadowSize: [40, 37], - shadowAnchor: [12, 35] - }); - this.map.defaultMarkerIconInfo = { - size: [21, 34], - icon - }; + createDefaultMarkerIcon(color: tinycolor.Instance, onMarkerIconReady) { + let icon: { size: number[], icon: Icon }; + if (!tinycolor.equals(color, this.settings.tinyColor)) { + icon = this.createColoredMarkerIcon(color); + } else { + if (!this.map.defaultMarkerIconInfo) { + this.map.defaultMarkerIconInfo = this.createColoredMarkerIcon(color); + } + icon = this.map.defaultMarkerIconInfo; } - onMarkerIconReady(this.map.defaultMarkerIconInfo); + onMarkerIconReady(icon); + } + + createColoredMarkerIcon(color: tinycolor.Instance): { size: number[], icon: Icon } { + return { + size: [21, 34], + icon: L.icon({ + iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + color.toHex(), + iconSize: [21, 34], + iconAnchor: [21 * this.markerOffset[0], 34 * this.markerOffset[1]], + popupAnchor: [0, -34], + shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow', + shadowSize: [40, 37], + shadowAnchor: [12, 35] + }) + }; } removeMarker() { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts index 5523ca2f03..5124490764 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts @@ -300,7 +300,8 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI id: datasource.entityId }, entityName: datasource.entityName, - entityLabel: datasource.entityLabel + entityLabel: datasource.entityLabel, + entityDescription: datasource.entityDescription }; } } diff --git a/ui-ngx/src/app/shared/models/query/query.models.ts b/ui-ngx/src/app/shared/models/query/query.models.ts index ae513da25f..a7647b3687 100644 --- a/ui-ngx/src/app/shared/models/query/query.models.ts +++ b/ui-ngx/src/app/shared/models/query/query.models.ts @@ -589,6 +589,10 @@ export const entityInfoFields: EntityKey[] = [ { type: EntityKeyType.ENTITY_FIELD, key: 'label' + }, + { + type: EntityKeyType.ENTITY_FIELD, + key: 'additionalInfo' } ]; @@ -609,7 +613,18 @@ export function entityDataToEntityInfo(entityData: EntityData): EntityInfo { } else { entityInfo.label = ''; } - entityInfo.entityDescription = 'TODO: Not implemented'; + entityInfo.entityDescription = ''; + if (fields.additionalInfo) { + const additionalInfo = fields.additionalInfo.value; + if (additionalInfo && additionalInfo.length) { + try { + const additionalInfoJson = JSON.parse(additionalInfo); + if (additionalInfoJson && additionalInfoJson.description) { + entityInfo.entityDescription = additionalInfoJson.description; + } + } catch (e) {} + } + } } return entityInfo; } From e7aae9664a91b2feb206587ed484030d9e66dd61 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 10 Aug 2020 15:34:23 +0300 Subject: [PATCH 12/18] Add new alias entity type - Current User Owner --- ui-ngx/src/app/core/http/entity.service.ts | 12 +++++++++++ .../entity/entity-autocomplete.component.ts | 21 +++++++++++++++++++ .../entity/entity-select.component.html | 3 ++- .../entity/entity-select.component.ts | 6 ++++-- .../app/shared/models/entity-type.models.ts | 10 ++++++++- .../assets/locale/locale.constant-en_US.json | 1 + 6 files changed, 49 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 7034029e93..0f5f1d554a 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -579,6 +579,9 @@ export class EntityService { } if (useAliasEntityTypes) { entityTypes.push(AliasEntityType.CURRENT_USER); + if (authUser.authority !== Authority.SYS_ADMIN) { + entityTypes.push(AliasEntityType.CURRENT_USER_OWNER); + } } if (allowedEntityTypes && allowedEntityTypes.length) { for (let index = entityTypes.length - 1; index >= 0; index--) { @@ -992,6 +995,15 @@ export class EntityService { const authUser = getCurrentAuthUser(this.store); entityId.entityType = EntityType.USER; entityId.id = authUser.userId; + } else if (entityType === AliasEntityType.CURRENT_USER_OWNER){ + const authUser = getCurrentAuthUser(this.store); + if (authUser.authority === Authority.TENANT_ADMIN) { + entityId.entityType = EntityType.TENANT; + entityId.id = authUser.tenantId; + } else if (authUser.authority === Authority.CUSTOMER_USER) { + entityId.entityType = EntityType.CUSTOMER; + entityId.id = authUser.customerId; + } } return entityId; } diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index e649a8702b..faeda98a10 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -26,6 +26,8 @@ import { BaseData } from '@shared/models/base-data'; import { EntityId } from '@shared/models/id/entity-id'; import { EntityService } from '@core/http/entity.service'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { getCurrentAuthUser } from '@core/auth/auth.selectors'; +import { Authority } from '@shared/models/authority.enum'; @Component({ selector: 'tb-entity-autocomplete', @@ -196,6 +198,18 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit this.noEntitiesMatchingText = 'customer.no-customers-matching'; this.entityRequiredText = 'customer.default-customer-required'; break; + case AliasEntityType.CURRENT_USER_OWNER: + const authUser = getCurrentAuthUser(this.store); + if (authUser.authority === Authority.TENANT_ADMIN) { + this.entityText = 'tenant.tenant'; + this.noEntitiesMatchingText = 'tenant.no-tenants-matching'; + this.entityRequiredText = 'tenant.tenant-required'; + } else { + this.entityText = 'customer.default-customer'; + this.noEntitiesMatchingText = 'customer.no-customers-matching'; + this.entityRequiredText = 'customer.default-customer-required'; + } + break; } } const currentEntity = this.getCurrentEntity(); @@ -328,6 +342,13 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit return EntityType.TENANT; } else if (entityType === AliasEntityType.CURRENT_USER) { return EntityType.USER; + } else if (entityType === AliasEntityType.CURRENT_USER_OWNER) { + const authUser = getCurrentAuthUser(this.store); + if (authUser.authority === Authority.TENANT_ADMIN) { + return EntityType.TENANT; + } else { + return EntityType.CUSTOMER; + } } return entityType; } diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-select.component.html index 8b75e1c00b..957633cd7f 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.html @@ -28,7 +28,8 @@ diff --git a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts index 0383d83c00..fb1c23e93a 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-select.component.ts @@ -97,7 +97,8 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte ngOnInit() { this.entitySelectFormGroup.get('entityType').valueChanges.subscribe( (value) => { - if(value === AliasEntityType.CURRENT_TENANT || value === AliasEntityType.CURRENT_USER) { + if(value === AliasEntityType.CURRENT_TENANT || value === AliasEntityType.CURRENT_USER || + value === AliasEntityType.CURRENT_USER_OWNER) { this.modelValue.id = NULL_UUID; } this.updateView(value, this.modelValue.id); @@ -147,7 +148,8 @@ export class EntitySelectComponent implements ControlValueAccessor, OnInit, Afte }; if (this.modelValue.entityType && (this.modelValue.id || this.modelValue.entityType === AliasEntityType.CURRENT_TENANT || - this.modelValue.entityType === AliasEntityType.CURRENT_USER)) { + this.modelValue.entityType === AliasEntityType.CURRENT_USER || + this.modelValue.entityType === AliasEntityType.CURRENT_USER_OWNER)) { this.propagateChange(this.modelValue); } else { this.propagateChange(null); diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 05e240581d..6841a911b7 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -51,7 +51,8 @@ export enum EntityType { export enum AliasEntityType { CURRENT_CUSTOMER = 'CURRENT_CUSTOMER', CURRENT_TENANT = 'CURRENT_TENANT', - CURRENT_USER = 'CURRENT_USER' + CURRENT_USER = 'CURRENT_USER', + CURRENT_USER_OWNER = 'CURRENT_USER_OWNER' } export interface EntityTypeTranslation { @@ -237,6 +238,13 @@ export const entityTypeTranslations = new Map Date: Mon, 10 Aug 2020 15:41:57 +0300 Subject: [PATCH 13/18] Fix autocomplete text for Current User Owner --- .../shared/components/entity/entity-autocomplete.component.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts index faeda98a10..86a45c8885 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-autocomplete.component.ts @@ -205,9 +205,9 @@ export class EntityAutocompleteComponent implements ControlValueAccessor, OnInit this.noEntitiesMatchingText = 'tenant.no-tenants-matching'; this.entityRequiredText = 'tenant.tenant-required'; } else { - this.entityText = 'customer.default-customer'; + this.entityText = 'customer.customer'; this.noEntitiesMatchingText = 'customer.no-customers-matching'; - this.entityRequiredText = 'customer.default-customer-required'; + this.entityRequiredText = 'customer.customer-required'; } break; } From 31d0425cd85cb1bbe76b5b96d770466dba6416f8 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 10 Aug 2020 16:09:38 +0300 Subject: [PATCH 14/18] Fix calculation value in function for map widget --- .../components/widget/lib/maps/maps-utils.ts | 28 ++++++++++++++++++- .../components/widget/lib/maps/polygon.ts | 9 ++---- .../components/widget/lib/maps/polyline.ts | 17 +++++------ 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts index 87b99d3499..813437a439 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts @@ -20,7 +20,15 @@ import { Datasource, DatasourceData } from '@app/shared/models/widget.models'; import _ from 'lodash'; import { Observable, Observer, of } from 'rxjs'; import { map } from 'rxjs/operators'; -import { createLabelFromDatasource, hashCode, isDefinedAndNotNull, isNumber, isUndefined, padValue } from '@core/utils'; +import { + createLabelFromDatasource, + hashCode, + isDefined, + isDefinedAndNotNull, isFunction, + isNumber, + isUndefined, + padValue +} from '@core/utils'; export function createTooltip(target: L.Layer, settings: MarkerSettings | PolylineSettings | PolygonSettings, @@ -403,6 +411,24 @@ export function safeExecute(func: (...args: any[]) => any, params = []) { return res; } +export function functionValueCalculator(useFunction: boolean, func: (...args: any[]) => any, params = [], defaultValue: any) { + let res; + if (useFunction && isDefined(func) && isFunction(func)) { + try { + res = func(...params); + if (!isDefinedAndNotNull(res) || res === '') { + res = defaultValue; + } + } catch (err) { + res = defaultValue; + console.log('error in external function:', err); + } + } else { + res = defaultValue; + } + return res; +} + export function calculateNewPointCoordinate(coordinate: number, imageSize: number): number { let pointCoordinate = coordinate / imageSize; if (pointCoordinate < 0) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts index 2af02b2878..f580e80b1b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts @@ -15,7 +15,7 @@ /// import L, { LatLngExpression, LeafletMouseEvent } from 'leaflet'; -import { createTooltip, parseWithTranslation, safeExecute } from './maps-utils'; +import { createTooltip, functionValueCalculator, parseWithTranslation, safeExecute } from './maps-utils'; import { FormattedData, PolygonSettings } from './map-models'; export class Polygon { @@ -97,10 +97,7 @@ export class Polygon { } private getPolygonColor(settings: PolygonSettings): string | null { - if (settings.usePolygonColorFunction) { - return safeExecute(settings.polygonColorFunction, [this.data, this.dataSources, this.data.dsIndex]); - } else { - return settings.polygonColor; - } + return functionValueCalculator(settings.usePolygonColorFunction, settings.polygonColorFunction, + [this.data, this.dataSources, this.data.dsIndex], settings.polygonColor); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts index 543f4a718a..6521257be5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts @@ -18,7 +18,7 @@ import L, { PolylineDecoratorOptions } from 'leaflet'; import 'leaflet-polylinedecorator'; import { FormattedData, PolylineSettings } from './map-models'; -import { safeExecute } from '@home/components/widget/lib/maps/maps-utils'; +import { functionValueCalculator, safeExecute } from '@home/components/widget/lib/maps/maps-utils'; export class Polyline { @@ -72,15 +72,12 @@ export class Polyline { getPolyStyle(settings: PolylineSettings): L.PolylineOptions { return { interactive: false, - color: settings.useColorFunction ? - safeExecute(settings.colorFunction, - [this.data, this.dataSources, this.data.dsIndex]) : settings.color, - opacity: settings.useStrokeOpacityFunction ? - safeExecute(settings.strokeOpacityFunction, - [this.data, this.dataSources, this.data.dsIndex]) : settings.strokeOpacity, - weight: settings.useStrokeWeightFunction ? - safeExecute(settings.strokeWeightFunction, - [this.data, this.dataSources, this.data.dsIndex]) : settings.strokeWeight, + color: functionValueCalculator(settings.useColorFunction, settings.colorFunction, + [this.data, this.dataSources, this.data.dsIndex], settings.color), + opacity: functionValueCalculator(settings.useStrokeOpacityFunction, settings.strokeOpacityFunction, + [this.data, this.dataSources, this.data.dsIndex], settings.strokeOpacity), + weight: functionValueCalculator(settings.useStrokeWeightFunction, settings.strokeWeightFunction, + [this.data, this.dataSources, this.data.dsIndex], settings.strokeWeight) } } From f5f6d4b917034914abf0eb80496ee516c469095f Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Mon, 10 Aug 2020 16:32:36 +0300 Subject: [PATCH 15/18] Fix update state id value --- ...anage-dashboard-states-dialog.component.ts | 20 ++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts index ad32146f87..c09292765b 100644 --- a/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/pages/dashboard/states/manage-dashboard-states-dialog.component.ts @@ -35,7 +35,7 @@ import { fromEvent, merge } from 'rxjs'; import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; import { TranslateService } from '@ngx-translate/core'; import { DialogService } from '@core/services/dialog.service'; -import { deepClone } from '@core/utils'; +import { deepClone, isUndefined } from '@core/utils'; import { DashboardStateDialogComponent, DashboardStateDialogData @@ -198,8 +198,9 @@ export class ManageDashboardStatesDialogComponent extends root: state.root, layouts: state.layouts }; - if (prevStateId) { - this.states[prevStateId] = newState; + if (prevStateId && prevStateId !== state.id) { + delete this.states[prevStateId]; + this.states[state.id] = newState; } else { this.states[state.id] = newState; } @@ -210,6 +211,19 @@ export class ManageDashboardStatesDialogComponent extends otherState.root = false; } } + } else { + let rootFound = false; + for (const id of Object.keys(this.states)) { + const otherState = this.states[id]; + if (otherState.root) { + rootFound = true; + break; + } + } + if (!rootFound) { + const firstStateId = Object.keys(this.states)[0]; + this.states[firstStateId].root = true; + } } this.onStatesUpdated(); } From e8e2f2119352193d06614c7ea554fecca6ae063e Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Mon, 10 Aug 2020 20:05:11 +0300 Subject: [PATCH 16/18] Map widgets fixes --- .../components/widget/lib/maps/leaflet-map.ts | 291 +++++++++--------- .../components/widget/lib/maps/map-models.ts | 45 +-- .../components/widget/lib/maps/map-widget2.ts | 9 +- .../components/widget/lib/maps/markers.ts | 8 +- .../widget/lib/maps/providers/google-map.ts | 2 +- .../widget/lib/maps/providers/here-map.ts | 2 +- .../widget/lib/maps/providers/image-map.ts | 2 +- .../widget/lib/maps/providers/index.ts | 52 +++- .../lib/maps/providers/openstreet-map.ts | 2 +- .../widget/lib/maps/providers/tencent-map.ts | 2 +- .../trip-animation.component.ts | 2 +- 11 files changed, 204 insertions(+), 213 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 e9d1170412..bf40ff3d82 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 @@ -38,13 +38,17 @@ import { UnitedMapSettings } from './map-models'; import { Marker } from './markers'; -import { BehaviorSubject, Observable, of } from 'rxjs'; -import { filter } from 'rxjs/operators'; +import { Observable, of } from 'rxjs'; import { Polyline } from './polyline'; import { Polygon } from './polygon'; -import { createLoadingDiv, createTooltip, parseArray, safeExecute } from '@home/components/widget/lib/maps/maps-utils'; +import { + createLoadingDiv, + createTooltip, + parseArray, + parseData, + safeExecute +} from '@home/components/widget/lib/maps/maps-utils'; import { WidgetContext } from '@home/models/widget-component.models'; -import { DatasourceData } from '@shared/models/widget.models'; import { deepClone, isDefinedAndNotEmptyStr } from '@core/utils'; export default abstract class LeafletMap { @@ -53,8 +57,6 @@ export default abstract class LeafletMap { polylines: Map = new Map(); polygons: Map = new Map(); map: L.Map; - map$: BehaviorSubject = new BehaviorSubject(null); - ready$: Observable = this.map$.pipe(filter(map => !!map)); options: UnitedMapSettings; bounds: L.LatLngBounds; datasources: FormattedData[]; @@ -69,6 +71,9 @@ export default abstract class LeafletMap { markerLabelText: string; replaceInfoTooltipMarker: Array = []; markerTooltipText: string; + drawRoutes: boolean; + showPolygon: boolean; + updatePending = false; protected constructor(public ctx: WidgetContext, public $container: HTMLElement, @@ -78,8 +83,7 @@ export default abstract class LeafletMap { public initSettings(options: MapSettings) { this.options.tinyColor = tinycolor(this.options.color || defaultSettings.color); - const { initCallback, - disableScrollZooming, + const { disableScrollZooming, useClusterMarkers, zoomOnClick, showCoverageOnHover, @@ -91,9 +95,6 @@ export default abstract class LeafletMap { if (disableScrollZooming) { this.map.scrollWheelZoom.disable(); } - if (initCallback) { - setTimeout(options.initCallback, 0); - } if (useClusterMarkers) { const clusteringSettings: MarkerClusterGroupOptions = { zoomToBoundsOnClick: zoomOnClick, @@ -109,7 +110,6 @@ export default abstract class LeafletMap { clusteringSettings.disableClusteringAtZoom = Math.floor(maxZoom); } this.markersCluster = markerClusterGroup(clusteringSettings); - this.ready$.subscribe(map => map.addLayer(this.markersCluster)); } } @@ -182,18 +182,16 @@ export default abstract class LeafletMap { public setLoading(loading: boolean) { if (this.loading !== loading) { this.loading = loading; - this.ready$.subscribe(() => { - if (this.loading) { - if (!this.loadingDiv) { - this.loadingDiv = createLoadingDiv(this.ctx.translate.instant('common.loading')); - } - this.$container.append(this.loadingDiv[0]); - } else { - if (this.loadingDiv) { - this.loadingDiv.remove(); - } + if (this.loading) { + if (!this.loadingDiv) { + this.loadingDiv = createLoadingDiv(this.ctx.translate.instant('common.loading')); } - }); + this.$container.append(this.loadingDiv[0]); + } else { + if (this.loadingDiv) { + this.loadingDiv.remove(); + } + } } } @@ -207,11 +205,13 @@ export default abstract class LeafletMap { if (this.options.draggableMarker) { this.addMarkerControl(); } - this.map$.next(this.map); - } - - public setDataSources(dataSources: FormattedData[]) { - this.datasources = dataSources; + if (this.options.useClusterMarkers) { + this.map.addLayer(this.markersCluster); + } + if (this.updatePending) { + this.updatePending = false; + this.updateData(this.drawRoutes, this.showPolygon); + } } public saveMarkerLocation(_e: FormattedData, lat?: number, lng?: number): Observable { @@ -259,7 +259,7 @@ export default abstract class LeafletMap { this.map.once('zoomend', () => { let minZoom = this.options.minZoomLevel; if (this.options.defaultZoomLevel) { - minZoom = Math.min(minZoom, this.options.defaultZoomLevel); + minZoom = Math.max(minZoom, this.options.defaultZoomLevel); } if (this.map.getZoom() > minZoom) { this.map.setZoom(minZoom, { animate: false }); @@ -297,8 +297,12 @@ export default abstract class LeafletMap { } } - updateData(data: DatasourceData[], formattedData: FormattedData[], drawRoutes: boolean, showPolygon: boolean) { - this.ready$.subscribe(() => { + updateData(drawRoutes: boolean, showPolygon: boolean) { + this.drawRoutes = drawRoutes; + this.showPolygon = showPolygon; + if (this.map) { + const data = this.ctx.data; + const formattedData = parseData(this.ctx.data); if (drawRoutes) { this.updatePolylines(parseArray(data), false); } @@ -306,23 +310,28 @@ export default abstract class LeafletMap { this.updatePolygons(formattedData, false); } this.updateMarkers(formattedData, false); - this.updateBoundsInternal(drawRoutes, showPolygon); - }); + this.updateBoundsInternal(); + if (this.options.draggableMarker) { + this.datasources = formattedData; + } + } else { + this.updatePending = true; + } } - private updateBoundsInternal(drawRoutes: boolean, showPolygon: boolean) { + private updateBoundsInternal() { const bounds = new L.LatLngBounds(null, null); - if (drawRoutes) { + if (this.drawRoutes) { this.polylines.forEach((polyline) => { bounds.extend(polyline.leafletPoly.getBounds()); }); } - if (showPolygon) { + if (this.showPolygon) { this.polygons.forEach((polygon) => { bounds.extend(polygon.leafletPoly.getBounds()); }); } - if ((this.options as MarkerSettings).useClusterMarkers) { + if ((this.options as MarkerSettings).useClusterMarkers && this.markersCluster.getBounds().isValid()) { bounds.extend(this.markersCluster.getBounds()); } else { this.markers.forEach((marker) => { @@ -331,7 +340,7 @@ export default abstract class LeafletMap { } const mapBounds = this.map.getBounds(); - if (bounds.isValid() && (!this.bounds || !mapBounds.contains(bounds))) { + if (bounds.isValid() && (!this.bounds || !this.bounds.isValid() || !this.bounds.equals(bounds) && !mapBounds.contains(bounds))) { this.bounds = bounds; this.fitBounds(bounds); } @@ -340,59 +349,57 @@ export default abstract class LeafletMap { // Markers updateMarkers(markersData: FormattedData[], updateBounds = true, callback?) { const rawMarkers = markersData.filter(mdata => !!this.convertPosition(mdata)); - this.ready$.subscribe(() => { - const toDelete = new Set(Array.from(this.markers.keys())); - const createdMarkers: Marker[] = []; - const updatedMarkers: Marker[] = []; - const deletedMarkers: Marker[] = []; - let m: Marker; - rawMarkers.forEach(data => { - if (data.rotationAngle || data.rotationAngle === 0) { - const currentImage = this.options.useMarkerImageFunction ? - safeExecute(this.options.markerImageFunction, - [data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage; - const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : ''; - this.options.icon = L.divIcon({ - html: `
` - }); - } else { - this.options.icon = null; - } - if (this.markers.get(data.entityName)) { - m = this.updateMarker(data.entityName, data, markersData, this.options); - if (m) { - updatedMarkers.push(m); - } - } else { - m = this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback); - if (m) { - createdMarkers.push(m); - } - } - toDelete.delete(data.entityName); + const toDelete = new Set(Array.from(this.markers.keys())); + const createdMarkers: Marker[] = []; + const updatedMarkers: Marker[] = []; + const deletedMarkers: Marker[] = []; + let m: Marker; + rawMarkers.forEach(data => { + if (data.rotationAngle || data.rotationAngle === 0) { + const currentImage = this.options.useMarkerImageFunction ? + safeExecute(this.options.markerImageFunction, + [data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage; + const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : ''; + this.options.icon = L.divIcon({ + html: `
` }); - toDelete.forEach((key) => { - m = this.deleteMarker(key); - if (m) { - deletedMarkers.push(m); - } - }); - this.markersData = markersData; - if ((this.options as MarkerSettings).useClusterMarkers) { - if (createdMarkers.length) { - this.markersCluster.addLayers(createdMarkers.map(marker => marker.leafletMarker)); - } - if (updatedMarkers.length) { - this.markersCluster.refreshClusters(updatedMarkers.map(marker => marker.leafletMarker)) - } - if (deletedMarkers.length) { - this.markersCluster.removeLayers(deletedMarkers.map(marker => marker.leafletMarker)); - } + } else { + this.options.icon = null; + } + if (this.markers.get(data.entityName)) { + m = this.updateMarker(data.entityName, data, markersData, this.options); + if (m) { + updatedMarkers.push(m); } - }); + } else { + m = this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback); + if (m) { + createdMarkers.push(m); + } + } + toDelete.delete(data.entityName); + }); + toDelete.forEach((key) => { + m = this.deleteMarker(key); + if (m) { + deletedMarkers.push(m); + } + }); + this.markersData = markersData; + if ((this.options as MarkerSettings).useClusterMarkers) { + if (createdMarkers.length) { + this.markersCluster.addLayers(createdMarkers.map(marker => marker.leafletMarker)); + } + if (updatedMarkers.length) { + this.markersCluster.refreshClusters(updatedMarkers.map(marker => marker.leafletMarker)) + } + if (deletedMarkers.length) { + this.markersCluster.removeLayers(deletedMarkers.map(marker => marker.leafletMarker)); + } + } } dragMarker = (e, data = {} as FormattedData) => { @@ -420,10 +427,8 @@ export default abstract class LeafletMap { private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings): Marker { const marker: Marker = this.markers.get(key); - const location = this.convertPosition(data) - if (!location.equals(marker.location)) { - marker.updateMarkerPosition(location); - } + const location = this.convertPosition(data); + marker.updateMarkerPosition(location); if (settings.showTooltip) { marker.updateMarkerTooltip(data); } @@ -445,26 +450,24 @@ export default abstract class LeafletMap { } updatePoints(pointsData: FormattedData[], getTooltip: (point: FormattedData, setTooltip?: boolean) => string) { - this.map$.subscribe(map => { - if (this.points) { - map.removeLayer(this.points); - } - 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); - }); - map.addLayer(this.points); - }); + if (this.points) { + this.map.removeLayer(this.points); + } + 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); + }); + this.map.addLayer(this.points); } // Polyline @@ -494,27 +497,23 @@ export default abstract class LeafletMap { } createPolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings, updateBounds = true) { - this.ready$.subscribe(() => { - const poly = new Polyline(this.map, - dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); - if (updateBounds) { - const bounds = poly.leafletPoly.getBounds(); - this.fitBounds(bounds); - } - this.polylines.set(data.entityName, poly); - }); + const poly = new Polyline(this.map, + dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); + if (updateBounds) { + const bounds = poly.leafletPoly.getBounds(); + this.fitBounds(bounds); + } + this.polylines.set(data.entityName, poly); } updatePolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings, updateBounds = true) { - this.ready$.subscribe(() => { - const poly = this.polylines.get(data.entityName); - const oldBounds = poly.leafletPoly.getBounds(); - poly.updatePolyline(dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); - const newBounds = poly.leafletPoly.getBounds(); - if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) { - this.fitBounds(newBounds); - } - }); + const poly = this.polylines.get(data.entityName); + const oldBounds = poly.leafletPoly.getBounds(); + poly.updatePolyline(dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings); + const newBounds = poly.leafletPoly.getBounds(); + if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) { + this.fitBounds(newBounds); + } } removePolyline(name: string) { @@ -560,26 +559,22 @@ export default abstract class LeafletMap { } createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) { - this.ready$.subscribe(() => { - const polygon = new Polygon(this.map, polyData, dataSources, settings); - if (updateBounds) { - const bounds = polygon.leafletPoly.getBounds(); - this.fitBounds(bounds); - } - this.polygons.set(polyData.entityName, polygon); - }); + const polygon = new Polygon(this.map, polyData, dataSources, settings); + if (updateBounds) { + const bounds = polygon.leafletPoly.getBounds(); + this.fitBounds(bounds); + } + this.polygons.set(polyData.entityName, polygon); } updatePolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) { - this.ready$.subscribe(() => { - const poly = this.polygons.get(polyData.entityName); - const oldBounds = poly.leafletPoly.getBounds(); - poly.updatePolygon(polyData, dataSources, settings); - const newBounds = poly.leafletPoly.getBounds(); - if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) { - this.fitBounds(newBounds); - } - }); + const poly = this.polygons.get(polyData.entityName); + const oldBounds = poly.leafletPoly.getBounds(); + poly.updatePolygon(polyData, dataSources, settings); + const newBounds = poly.leafletPoly.getBounds(); + if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) { + this.fitBounds(newBounds); + } } removePolygon(name: string) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts index d0da933e8a..445f7e6835 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts @@ -15,14 +15,7 @@ /// import { LatLngTuple } from 'leaflet'; -import { Datasource, JsonSettingsSchema } from '@app/shared/models/widget.models'; -import { Type } from '@angular/core'; -import LeafletMap from './leaflet-map'; -import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers'; -import { - openstreetMapSettingsSchema, tencentMapSettingsSchema, - googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema -} from './schemes'; +import { Datasource } from '@app/shared/models/widget.models'; import { EntityType } from '@shared/models/entity-type.models'; import tinycolor from 'tinycolor2'; @@ -30,12 +23,10 @@ export const DEFAULT_MAP_PAGE_SIZE = 16384; export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; -export type GetTooltip = (point: FormattedData, setTooltip?: boolean) => string; export type PosFuncton = (origXPos, origYPos) => { x, y }; export type MapSettings = { draggableMarker: boolean; - initCallback?: () => any; posFunction: PosFuncton; defaultZoomLevel?: number; disableScrollZooming?: boolean; @@ -209,40 +200,6 @@ export type actionsHandler = ($event: Event, datasource: Datasource) => void; export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings & TripAnimationSettings; -interface IProvider { - MapClass: Type, - schema: JsonSettingsSchema, - name: string -} - -export const providerSets: { [key: string]: IProvider } = { - 'openstreet-map': { - MapClass: OpenStreetMap, - schema: openstreetMapSettingsSchema, - name: 'openstreet-map', - }, - 'tencent-map': { - MapClass: TencentMap, - schema: tencentMapSettingsSchema, - name: 'tencent-map' - }, - 'google-map': { - MapClass: GoogleMap, - schema: googleMapSettingsSchema, - name: 'google-map' - }, - here: { - MapClass: HEREMap, - schema: hereMapSettingsSchema, - name: 'here' - }, - 'image-map': { - MapClass: ImageMap, - schema: imageMapSettingsSchema, - name: 'image-map' - } -}; - export const defaultSettings: any = { xPosKeyName: 'xPos', yPosKeyName: 'yPos', diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts index 7acc6e92e5..27d8b56585 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts @@ -15,12 +15,10 @@ /// import { - DEFAULT_MAP_PAGE_SIZE, defaultSettings, FormattedData, hereProviders, MapProviders, - providerSets, UnitedMapSettings } from './map-models'; import LeafletMap from './leaflet-map'; @@ -46,6 +44,7 @@ import _ from 'lodash'; import { EntityDataPageLink } from '@shared/models/query/query.models'; import { isDefined } from '@core/utils'; import { forkJoin, Observable, of } from 'rxjs'; +import { providerSets } from '@home/components/widget/lib/maps/providers'; // @dynamic export class MapWidgetController implements MapWidgetInterface { @@ -275,11 +274,7 @@ export class MapWidgetController implements MapWidgetInterface { } update() { - const formattedData = parseData(this.data); - this.map.updateData(this.data, formattedData, this.drawRoutes, this.settings.showPolygon); - if (this.settings.draggableMarker) { - this.map.setDataSources(formattedData); - } + this.map.updateData(this.drawRoutes, this.settings.showPolygon); this.map.setLoading(false); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts index efad5931c9..f5f7663c39 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts @@ -34,11 +34,10 @@ export class Marker { tooltipOffset: L.LatLngTuple; markerOffset: L.LatLngTuple; tooltip: L.Popup; - location: L.LatLngExpression; data: FormattedData; dataSources: FormattedData[]; - constructor(private map: LeafletMap, location: L.LatLngExpression, public settings: MarkerSettings, + constructor(private map: LeafletMap, private location: L.LatLng, public settings: MarkerSettings, data?: FormattedData, dataSources?, onDragendListener?) { this.setDataSources(data, dataSources); this.leafletMarker = L.marker(location, { @@ -94,8 +93,11 @@ export class Marker { } } - updateMarkerPosition(position: L.LatLngExpression) { + updateMarkerPosition(position: L.LatLng) { + if (!this.location.equals(position)) { + this.location = position; this.leafletMarker.setLatLng(position); + } } updateMarkerLabel(settings: MarkerSettings) { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts index b0254ee2c2..021695d860 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts @@ -35,6 +35,7 @@ export class GoogleMap extends LeafletMap { constructor(ctx: WidgetContext, $container, options: UnitedMapSettings) { super(ctx, $container, options); this.resource = ctx.$injector.get(ResourcesService); + super.initSettings(options); this.loadGoogle(() => { const map = L.map($container, {attributionControl: false}).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); (L.gridLayer as any).googleMutant({ @@ -42,7 +43,6 @@ export class GoogleMap extends LeafletMap { }).addTo(map); super.setMap(map); }, options.gmApiKey); - super.initSettings(options); } private loadGoogle(callback, apiKey = 'AIzaSyDoEx2kaGz3PxwbI9T7ccTSg5xjdw8Nw8Q') { diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts index 17a8f61801..882c85861b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/here-map.ts @@ -25,7 +25,7 @@ export class HEREMap extends LeafletMap { const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); const tileLayer = (L.tileLayer as any).provider(options.mapProviderHere || 'HERE.normalDay', options.credentials); tileLayer.addTo(map); - super.setMap(map); super.initSettings(options); + super.setMap(map); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts index 82f8120bac..9ffe891a51 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts @@ -47,8 +47,8 @@ export class ImageMap extends LeafletMap { this.onResize(true); } else { this.onResize(); - super.setMap(this.map); super.initSettings(options); + super.setMap(this.map); } }); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/index.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/index.ts index 16d411c167..c9e7bbe8f9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/index.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/index.ts @@ -14,8 +14,50 @@ /// limitations under the License. /// -export * from './tencent-map'; -export * from './google-map'; -export * from './here-map'; -export * from './image-map'; -export * from './openstreet-map'; +import { + googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema, + openstreetMapSettingsSchema, + tencentMapSettingsSchema +} from '@home/components/widget/lib/maps/schemes'; +import { OpenStreetMap } from './openstreet-map'; +import { TencentMap } from './tencent-map'; +import { GoogleMap } from './google-map'; +import { HEREMap } from './here-map'; +import { ImageMap } from './image-map'; +import { Type } from '@angular/core'; +import LeafletMap from '@home/components/widget/lib/maps/leaflet-map'; +import { JsonSettingsSchema } from '@shared/models/widget.models'; + +interface IProvider { + MapClass: Type, + schema: JsonSettingsSchema, + name: string +} + +export const providerSets: { [key: string]: IProvider } = { + 'openstreet-map': { + MapClass: OpenStreetMap, + schema: openstreetMapSettingsSchema, + name: 'openstreet-map', + }, + 'tencent-map': { + MapClass: TencentMap, + schema: tencentMapSettingsSchema, + name: 'tencent-map' + }, + 'google-map': { + MapClass: GoogleMap, + schema: googleMapSettingsSchema, + name: 'google-map' + }, + here: { + MapClass: HEREMap, + schema: hereMapSettingsSchema, + name: 'here' + }, + 'image-map': { + MapClass: ImageMap, + schema: imageMapSettingsSchema, + name: 'image-map' + } +}; diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts index d1e2379dbd..0d5e4c68dd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/openstreet-map.ts @@ -29,7 +29,7 @@ export class OpenStreetMap extends LeafletMap { else tileLayer = (L.tileLayer as any).provider(options.mapProvider || 'OpenStreetMap.Mapnik'); tileLayer.addTo(map); - super.setMap(map); super.initSettings(options); + super.setMap(map); } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts index a615de883a..74a2447726 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/tencent-map.ts @@ -31,7 +31,7 @@ export class TencentMap extends LeafletMap { attribution: '©2020 Tencent - GS(2018)2236号- Data© NavInfo' }).addTo(map); txLayer.addTo(map); - super.setMap(map); super.initSettings(options); + super.setMap(map); } } 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 25912c1747..99c399d3f2 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 @@ -159,7 +159,7 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy } this.calcLabel(); this.calcTooltip(currentPosition.find(position => position.entityName === this.activeTrip.entityName)); - if (this.mapWidget) { + if (this.mapWidget && this.mapWidget.map && this.mapWidget.map.map) { this.mapWidget.map.updatePolylines(this.interpolatedTimeData.map(ds => _.values(ds)), true, this.activeTrip); if (this.settings.showPolygon) { this.mapWidget.map.updatePolygons(this.interpolatedTimeData); From 10dd5c352f107cda96d2b85691724372fffdded1 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 11 Aug 2020 10:39:13 +0300 Subject: [PATCH 17/18] Max entities for data subscription over WS --- ...efaultTbEntityDataSubscriptionService.java | 20 +++++++++++-------- .../subscription/TbAbstractDataSubCtx.java | 7 ++++--- .../subscription/TbEntityDataSubCtx.java | 18 +++++++---------- .../telemetry/cmd/v2/EntityDataUpdate.java | 7 ++++++- .../src/main/resources/thingsboard.yml | 3 ++- 5 files changed, 31 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index 39c9c66a98..a968eb47d8 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -128,6 +128,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc private long dynamicPageLinkRefreshInterval; @Value("${server.ws.dynamic_page_link.refresh_pool_size:1}") private int dynamicPageLinkRefreshPoolSize; + @Value("${server.ws.max_entities_per_data_subscription:1000}") + private int maxEntitiesPerDataSubscription; @Value("${server.ws.max_entities_per_alarm_subscription:1000}") private int maxEntitiesPerAlarmSubscription; @@ -220,7 +222,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc } else if (cmd.getTsCmd() != null) { handleTimeSeriesCmd(theCtx, cmd.getTsCmd()); } else if (!theCtx.isInitialDataSent()) { - EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null); + EntityDataUpdate update = new EntityDataUpdate(theCtx.getCmdId(), theCtx.getData(), null, theCtx.getMaxEntitiesPerDataSubscription()); wsService.sendWsMsg(theCtx.getSessionId(), update); theCtx.setInitialDataSent(true); } @@ -298,7 +300,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc private TbEntityDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, EntityDataCmd cmd) { Map sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); - TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmd.getCmdId()); + TbEntityDataSubCtx ctx = new TbEntityDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, + attributesService, stats, sessionRef, cmd.getCmdId(), maxEntitiesPerDataSubscription); ctx.setAndResolveQuery(cmd.getQuery()); sessionSubs.put(cmd.getCmdId(), ctx); return ctx; @@ -306,7 +309,8 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc private TbAlarmDataSubCtx createSubCtx(TelemetryWebSocketSessionRef sessionRef, AlarmDataCmd cmd) { Map sessionSubs = subscriptionsBySessionId.computeIfAbsent(sessionRef.getSessionId(), k -> new HashMap<>()); - TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription); + TbAlarmDataSubCtx ctx = new TbAlarmDataSubCtx(serviceId, wsService, entityService, localSubscriptionService, + attributesService, stats, alarmService, sessionRef, cmd.getCmdId(), maxEntitiesPerAlarmSubscription); ctx.setAndResolveQuery(cmd.getQuery()); sessionSubs.put(cmd.getCmdId(), ctx); return ctx; @@ -372,10 +376,10 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc }); EntityDataUpdate update; if (!ctx.isInitialDataSent()) { - update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null); + update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); ctx.setInitialDataSent(true); } else { - update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData()); + update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription()); } wsService.sendWsMsg(ctx.getSessionId(), update); if (subscribe) { @@ -422,10 +426,10 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc }); EntityDataUpdate update; if (!ctx.isInitialDataSent()) { - update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null); + update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); ctx.setInitialDataSent(true); } else { - update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData()); + update = new EntityDataUpdate(ctx.getCmdId(), null, ctx.getData().getData(), ctx.getMaxEntitiesPerDataSubscription()); } wsService.sendWsMsg(ctx.getSessionId(), update); ctx.createSubscriptions(latestCmd.getKeys(), true); @@ -440,7 +444,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc }, wsCallBackExecutor); } else { if (!ctx.isInitialDataSent()) { - EntityDataUpdate update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null); + EntityDataUpdate update = new EntityDataUpdate(ctx.getCmdId(), ctx.getData(), null, ctx.getMaxEntitiesPerDataSubscription()); wsService.sendWsMsg(ctx.getSessionId(), update); ctx.setInitialDataSent(true); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java index cd09ea9d44..41fbfd7ab2 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java @@ -57,6 +57,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ScheduledFuture; import java.util.function.Function; @@ -98,9 +99,9 @@ public abstract class TbAbstractDataSubCtx(); - this.subToDynamicValueKeySet = new HashSet<>(); - this.dynamicValues = new HashMap<>(); + this.subToEntityIdMap = new ConcurrentHashMap<>(); + this.subToDynamicValueKeySet = ConcurrentHashMap.newKeySet(); + this.dynamicValues = new ConcurrentHashMap<>(); } public void setAndResolveQuery(T query) { diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java index 0e58c07994..c43ee664fc 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java @@ -15,13 +15,10 @@ */ package org.thingsboard.server.service.subscription; -import lombok.AllArgsConstructor; -import lombok.Data; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataQuery; import org.thingsboard.server.common.data.query.EntityKey; @@ -38,8 +35,6 @@ import org.thingsboard.server.service.telemetry.cmd.v2.TimeSeriesCmd; import org.thingsboard.server.service.telemetry.sub.TelemetrySubscriptionUpdate; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -48,8 +43,6 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.concurrent.ScheduledFuture; -import java.util.function.Function; import java.util.stream.Collectors; @Slf4j @@ -63,11 +56,14 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { private boolean initialDataSent; private TimeSeriesCmd curTsCmd; private LatestValueCmd latestValueCmd; + @Getter + private final int maxEntitiesPerDataSubscription; public TbEntityDataSubCtx(String serviceId, TelemetryWebSocketService wsService, EntityService entityService, TbLocalSubscriptionService localSubscriptionService, AttributesService attributesService, - SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId) { + SubscriptionServiceStatistics stats, TelemetryWebSocketSessionRef sessionRef, int cmdId, int maxEntitiesPerDataSubscription) { super(serviceId, wsService, entityService, localSubscriptionService, attributesService, stats, sessionRef, cmdId); + this.maxEntitiesPerDataSubscription = maxEntitiesPerDataSubscription; } @Override @@ -120,7 +116,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { if (!latestUpdate.isEmpty()) { Map> latestMap = Collections.singletonMap(keyType, latestUpdate); entityData = new EntityData(entityId, latestMap, null); - wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData))); + wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData), maxEntitiesPerDataSubscription)); } } @@ -163,7 +159,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { Map tsMap = new HashMap<>(); tsUpdate.forEach((key, tsValue) -> tsMap.put(key, tsValue.toArray(new TsValue[tsValue.size()]))); entityData = new EntityData(entityId, null, tsMap); - wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData))); + wsService.sendWsMsg(sessionId, new EntityDataUpdate(cmdId, null, Collections.singletonList(entityData), maxEntitiesPerDataSubscription)); } } @@ -207,7 +203,7 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { ); } } - wsService.sendWsMsg(sessionRef.getSessionId(), new EntityDataUpdate(cmdId, data, null)); + wsService.sendWsMsg(sessionRef.getSessionId(), new EntityDataUpdate(cmdId, data, null, maxEntitiesPerDataSubscription)); subIdsToCancel.forEach(subId -> localSubscriptionService.cancelSubscription(getSessionId(), subId)); subsToAdd.forEach(localSubscriptionService::addSubscription); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java b/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java index 81a1ee1778..1467090874 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.telemetry.cmd.v2; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Getter; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.service.telemetry.sub.SubscriptionErrorCode; @@ -27,8 +28,12 @@ import java.util.List; public class EntityDataUpdate extends DataUpdate { - public EntityDataUpdate(int cmdId, PageData data, List update) { + @Getter + private long allowedEntities; + + public EntityDataUpdate(int cmdId, PageData data, List update, long allowedEntities) { super(cmdId, data, update, SubscriptionErrorCode.NO_ERROR.getCode(), null); + this.allowedEntities = allowedEntities; } public EntityDataUpdate(int cmdId, int errorCode, String errorMsg) { diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 54a3dac870..9d5897b483 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -50,7 +50,8 @@ server: refresh_interval: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_INTERVAL_SEC:60}" refresh_pool_size: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_REFRESH_POOL_SIZE:1}" max_per_user: "${TB_SERVER_WS_DYNAMIC_PAGE_LINK_MAX_PER_USER:10}" - max_entities_per_alarm_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_ALARM_SUBSCRIPTION:1000}" + max_entities_per_data_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_DATA_SUBSCRIPTION:10000}" + max_entities_per_alarm_subscription: "${TB_SERVER_WS_MAX_ENTITIES_PER_ALARM_SUBSCRIPTION:10000}" rest: limits: tenant: From 2dac896f1bfbedd4345cb5c2cc502559078a673e Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 11 Aug 2020 11:32:42 +0300 Subject: [PATCH 18/18] Additional Info fix --- .../server/dao/sql/query/DefaultEntityQueryRepository.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index b3e2361da2..95649e007e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -200,7 +200,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { " THEN (select additional_info from device where id = entity_id)" + " WHEN entity.entity_type = 'ENTITY_VIEW'" + " THEN (select additional_info from entity_view where id = entity_id)" + - " END as label"; + " END as additional_info"; static { entityTableMap.put(EntityType.ASSET, "asset"); @@ -460,7 +460,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { //TODO: fetch last level only. //TODO: fetch distinct records. String lvlFilter = getLvlFilter(entityFilter.getMaxLevel()); - String selectFields = "SELECT tenant_id, customer_id, id, created_time, type, name " + String selectFields = "SELECT tenant_id, customer_id, id, created_time, type, name, additional_info " + (entityType.equals(EntityType.ENTITY_VIEW) ? "" : ", label ") + "FROM " + entityType.name() + " WHERE id in ( SELECT entity_id"; String from = getQueryTemplate(entityFilter.getDirection());