Commit 0533416982b290dccb8edbe4fef387a4d53488ed

Authored by Andrii Shvaika
2 parents 4592ab89 dcf94183

Merge branch 'feature/entity-data-query' of github.com:thingsboard/thingsboard i…

…nto feature/entity-data-query
... ... @@ -282,6 +282,9 @@ export interface IWidgetSubscription {
282 282
283 283 subscribe(): void;
284 284
  285 + subscribeAllForPaginatedData(pageLink: EntityDataPageLink,
  286 + keyFilters: KeyFilter[]): void;
  287 +
285 288 subscribeForPaginatedData(datasourceIndex: number,
286 289 pageLink: EntityDataPageLink,
287 290 keyFilters: KeyFilter[]): void;
... ...
... ... @@ -884,6 +884,13 @@ export class WidgetSubscription implements IWidgetSubscription {
884 884 }
885 885 }
886 886
  887 + subscribeAllForPaginatedData(pageLink: EntityDataPageLink,
  888 + keyFilters: KeyFilter[]): void {
  889 + this.configuredDatasources.forEach((datasource, datasourceIndex) => {
  890 + this.subscribeForPaginatedData(datasourceIndex, pageLink, keyFilters);
  891 + });
  892 + }
  893 +
887 894 subscribeForPaginatedData(datasourceIndex: number,
888 895 pageLink: EntityDataPageLink,
889 896 keyFilters: KeyFilter[]): void {
... ...
... ... @@ -32,8 +32,9 @@ import { BehaviorSubject, Observable } from 'rxjs';
32 32 import { filter } from 'rxjs/operators';
33 33 import { Polyline } from './polyline';
34 34 import { Polygon } from './polygon';
35   -import { createTooltip, safeExecute } from '@home/components/widget/lib/maps/maps-utils';
  35 +import { createTooltip, parseArray, parseData, safeExecute } from '@home/components/widget/lib/maps/maps-utils';
36 36 import { WidgetContext } from '@home/models/widget-component.models';
  37 +import { DatasourceData } from '@shared/models/widget.models';
37 38
38 39 export default abstract class LeafletMap {
39 40
... ... @@ -247,32 +248,75 @@ export default abstract class LeafletMap {
247 248 }
248 249 }
249 250
250   - // Markers
251   - updateMarkers(markersData: FormattedData[], callback?) {
252   - markersData.filter(mdata => !!this.convertPosition(mdata)).forEach(data => {
  251 + updateData(data: DatasourceData[], drawRoutes: boolean, showPolygon: boolean) {
  252 + this.ready$.subscribe(() => {
  253 + if (drawRoutes) {
  254 + this.updatePolylines(parseArray(data), false);
  255 + }
  256 + if (showPolygon) {
  257 + this.updatePolygons(parseData(data), false);
  258 + }
  259 + this.updateMarkers(parseData(data), false);
  260 + this.updateBoundsInternal(drawRoutes, showPolygon);
  261 + });
  262 + }
  263 +
  264 + private updateBoundsInternal(drawRoutes: boolean, showPolygon: boolean) {
  265 + this.bounds = new L.LatLngBounds(null, null);
  266 + if (drawRoutes) {
  267 + this.polylines.forEach((polyline) => {
  268 + this.bounds.extend(polyline.leafletPoly.getBounds());
  269 + });
  270 + }
  271 + if (showPolygon) {
  272 + this.polygons.forEach((polygon) => {
  273 + this.bounds.extend(polygon.leafletPoly.getBounds());
  274 + });
  275 + }
  276 + this.markers.forEach((marker) => {
  277 + this.bounds.extend(marker.leafletMarker.getLatLng());
  278 + });
  279 + this.fitBounds(this.bounds);
  280 + }
  281 +
  282 + // Markers
  283 + updateMarkers(markersData: FormattedData[], updateBounds = true, callback?) {
  284 + const rawMarkers = markersData.filter(mdata => !!this.convertPosition(mdata));
  285 + this.ready$.subscribe(() => {
  286 + const keys: string[] = [];
  287 + rawMarkers.forEach(data => {
253 288 if (data.rotationAngle || data.rotationAngle === 0) {
254   - const currentImage = this.options.useMarkerImageFunction ?
255   - safeExecute(this.options.markerImageFunction,
256   - [data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage;
257   - const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : '';
258   - this.options.icon = L.divIcon({
259   - html: `<div class="arrow"
  289 + const currentImage = this.options.useMarkerImageFunction ?
  290 + safeExecute(this.options.markerImageFunction,
  291 + [data, this.options.markerImages, markersData, data.dsIndex]) : this.options.currentImage;
  292 + const style = currentImage ? 'background-image: url(' + currentImage.url + ');' : '';
  293 + this.options.icon = L.divIcon({
  294 + html: `<div class="arrow"
260 295 style="transform: translate(-10px, -10px)
261 296 rotate(${data.rotationAngle}deg);
262 297 ${style}"><div>`
263   - });
264   - }
265   - else {
266   - this.options.icon = null;
  298 + });
  299 + } else {
  300 + this.options.icon = null;
267 301 }
268 302 if (this.markers.get(data.entityName)) {
269   - this.updateMarker(data.entityName, data, markersData, this.options)
  303 + this.updateMarker(data.entityName, data, markersData, this.options)
  304 + } else {
  305 + this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, updateBounds, callback);
270 306 }
271   - else {
272   - this.createMarker(data.entityName, data, markersData, this.options as MarkerSettings, callback);
  307 + keys.push(data.entityName);
  308 + });
  309 + const toDelete: string[] = [];
  310 + this.markers.forEach((v, mKey) => {
  311 + if (!keys.includes(mKey)) {
  312 + toDelete.push(mKey);
273 313 }
  314 + });
  315 + toDelete.forEach((key) => {
  316 + this.deleteMarker(key);
  317 + });
  318 + this.markersData = markersData;
274 319 });
275   - this.markersData = markersData;
276 320 }
277 321
278 322 dragMarker = (e, data = {}) => {
... ... @@ -280,21 +324,20 @@ export default abstract class LeafletMap {
280 324 this.saveMarkerLocation({ ...data, ...this.convertToCustomFormat(e.target._latlng) });
281 325 }
282 326
283   - private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings, callback?) {
284   - this.ready$.subscribe(() => {
285   - const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker);
286   - if (callback)
287   - newMarker.leafletMarker.on('click', () => { callback(data, true) });
288   - if (this.bounds)
289   - this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()));
290   - this.markers.set(key, newMarker);
291   - if (this.options.useClusterMarkers) {
292   - this.markersCluster.addLayer(newMarker.leafletMarker);
293   - }
294   - else {
295   - this.map.addLayer(newMarker.leafletMarker);
296   - }
297   - });
  327 + private createMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings,
  328 + updateBounds = true, callback?) {
  329 + const newMarker = new Marker(this.convertPosition(data), settings, data, dataSources, this.dragMarker);
  330 + if (callback)
  331 + newMarker.leafletMarker.on('click', () => { callback(data, true) });
  332 + if (this.bounds && updateBounds)
  333 + this.fitBounds(this.bounds.extend(newMarker.leafletMarker.getLatLng()));
  334 + this.markers.set(key, newMarker);
  335 + if (this.options.useClusterMarkers) {
  336 + this.markersCluster.addLayer(newMarker.leafletMarker);
  337 + }
  338 + else {
  339 + this.map.addLayer(newMarker.leafletMarker);
  340 + }
298 341 }
299 342
300 343 private updateMarker(key: string, data: FormattedData, dataSources: FormattedData[], settings: MarkerSettings) {
... ... @@ -306,8 +349,9 @@ export default abstract class LeafletMap {
306 349 if (settings.showTooltip) {
307 350 marker.updateMarkerTooltip(data);
308 351 }
309   - if (settings.useClusterMarkers)
310   - this.markersCluster.refreshClusters()
  352 + if (settings.useClusterMarkers) {
  353 + this.markersCluster.refreshClusters()
  354 + }
311 355 marker.setDataSources(data, dataSources);
312 356 marker.updateMarkerIcon(settings);
313 357 }
... ... @@ -315,7 +359,11 @@ export default abstract class LeafletMap {
315 359 deleteMarker(key: string) {
316 360 let marker = this.markers.get(key)?.leafletMarker;
317 361 if (marker) {
318   - this.map.removeLayer(marker);
  362 + if (this.options.useClusterMarkers) {
  363 + this.markersCluster.removeLayer(marker);
  364 + } else {
  365 + this.map.removeLayer(marker);
  366 + }
319 367 this.markers.delete(key);
320 368 marker = null;
321 369 }
... ... @@ -346,41 +394,49 @@ export default abstract class LeafletMap {
346 394
347 395 // Polyline
348 396
349   - updatePolylines(polyData: FormattedData[][], data?: FormattedData) {
  397 + updatePolylines(polyData: FormattedData[][], updateBounds = true, data?: FormattedData) {
  398 + const keys: string[] = [];
350 399 polyData.forEach((dataSource: FormattedData[]) => {
351 400 data = data || dataSource[0];
352 401 if (dataSource.length && data.entityName === dataSource[0].entityName) {
353 402 if (this.polylines.get(data.entityName)) {
354   - this.updatePolyline(data, dataSource, this.options);
  403 + this.updatePolyline(data, dataSource, this.options, updateBounds);
  404 + } else {
  405 + this.createPolyline(data, dataSource, this.options, updateBounds);
355 406 }
356   - else {
357   - this.createPolyline(data, dataSource, this.options);
358   - }
359   - }
360   - else {
361   - if (data)
362   - this.removePolyline(dataSource[0]?.entityName)
  407 + keys.push(data.entityName);
363 408 }
364   - })
  409 + });
  410 + const toDelete: string[] = [];
  411 + this.polylines.forEach((v, mKey) => {
  412 + if (!keys.includes(mKey)) {
  413 + toDelete.push(mKey);
  414 + }
  415 + });
  416 + toDelete.forEach((key) => {
  417 + this.removePolyline(key);
  418 + });
365 419 }
366 420
367   - createPolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings) {
  421 + createPolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings, updateBounds = true) {
368 422 this.ready$.subscribe(() => {
369 423 const poly = new Polyline(this.map,
370 424 dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
371   - const bounds = poly.leafletPoly.getBounds();
372   - this.fitBounds(bounds);
  425 + if (updateBounds) {
  426 + const bounds = poly.leafletPoly.getBounds();
  427 + this.fitBounds(bounds);
  428 + }
373 429 this.polylines.set(data.entityName, poly);
374 430 });
375 431 }
376 432
377   - updatePolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings) {
  433 + updatePolyline(data: FormattedData, dataSources: FormattedData[], settings: PolylineSettings, updateBounds = true) {
378 434 this.ready$.subscribe(() => {
379 435 const poly = this.polylines.get(data.entityName);
380 436 const oldBounds = poly.leafletPoly.getBounds();
381 437 poly.updatePolyline(dataSources.map(el => this.convertPosition(el)).filter(el => !!el), data, dataSources, settings);
382 438 const newBounds = poly.leafletPoly.getBounds();
383   - if (oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
  439 + if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
384 440 this.fitBounds(newBounds);
385 441 }
386 442 });
... ... @@ -399,40 +455,60 @@ export default abstract class LeafletMap {
399 455
400 456 // Polygon
401 457
402   - updatePolygons(polyData: FormattedData[]) {
  458 + updatePolygons(polyData: FormattedData[], updateBounds = true) {
  459 + const keys: string[] = [];
403 460 polyData.forEach((data: FormattedData) => {
404 461 if (data && data.hasOwnProperty(this.options.polygonKeyName)) {
405 462 if (typeof (data[this.options.polygonKeyName]) === 'string') {
406 463 data[this.options.polygonKeyName] = JSON.parse(data[this.options.polygonKeyName]) as LatLngTuple[];
407 464 }
408   - if (this.polygons.get(data.$datasource.entityName)) {
409   - this.updatePolygon(data, polyData, this.options);
410   - }
411   - else {
412   - this.createPolygon(data, polyData, this.options);
  465 + if (this.polygons.get(data.entityName)) {
  466 + this.updatePolygon(data, polyData, this.options, updateBounds);
  467 + } else {
  468 + this.createPolygon(data, polyData, this.options, updateBounds);
413 469 }
  470 + keys.push(data.entityName);
414 471 }
415 472 });
  473 + const toDelete: string[] = [];
  474 + this.polygons.forEach((v, mKey) => {
  475 + if (!keys.includes(mKey)) {
  476 + toDelete.push(mKey);
  477 + }
  478 + });
  479 + toDelete.forEach((key) => {
  480 + this.removePolygon(key);
  481 + });
416 482 }
417 483
418   - createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) {
  484 + createPolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) {
419 485 this.ready$.subscribe(() => {
420 486 const polygon = new Polygon(this.map, polyData, dataSources, settings);
421   - const bounds = polygon.leafletPoly.getBounds();
422   - this.fitBounds(bounds);
423   - this.polygons.set(polyData.$datasource.entityName, polygon);
  487 + if (updateBounds) {
  488 + const bounds = polygon.leafletPoly.getBounds();
  489 + this.fitBounds(bounds);
  490 + }
  491 + this.polygons.set(polyData.entityName, polygon);
424 492 });
425 493 }
426 494
427   - updatePolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings) {
  495 + updatePolygon(polyData: FormattedData, dataSources: FormattedData[], settings: PolygonSettings, updateBounds = true) {
428 496 this.ready$.subscribe(() => {
429 497 const poly = this.polygons.get(polyData.entityName);
430 498 const oldBounds = poly.leafletPoly.getBounds();
431 499 poly.updatePolygon(polyData, dataSources, settings);
432 500 const newBounds = poly.leafletPoly.getBounds();
433   - if (oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
  501 + if (updateBounds && oldBounds.toBBoxString() !== newBounds.toBBoxString()) {
434 502 this.fitBounds(newBounds);
435 503 }
436 504 });
437 505 }
  506 +
  507 + removePolygon(name: string) {
  508 + const poly = this.polygons.get(name);
  509 + if (poly) {
  510 + this.map.removeLayer(poly.leafletPoly);
  511 + this.polygons.delete(name);
  512 + }
  513 + }
438 514 }
... ...
... ... @@ -24,6 +24,8 @@ import {
24 24 googleMapSettingsSchema, hereMapSettingsSchema, imageMapSettingsSchema
25 25 } from './schemes';
26 26
  27 +export const DEFAULT_MAP_PAGE_SIZE = 1024;
  28 +
27 29 export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
28 30 export type MarkerImageFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
29 31 export type GetTooltip = (point: FormattedData, setTooltip?: boolean) => string;
... ...
... ... @@ -14,7 +14,14 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { defaultSettings, hereProviders, MapProviders, providerSets, UnitedMapSettings } from './map-models';
  17 +import {
  18 + DEFAULT_MAP_PAGE_SIZE,
  19 + defaultSettings,
  20 + hereProviders,
  21 + MapProviders,
  22 + providerSets,
  23 + UnitedMapSettings
  24 +} from './map-models';
18 25 import LeafletMap from './leaflet-map';
19 26 import {
20 27 commonMapSettingsSchema,
... ... @@ -35,6 +42,7 @@ import { AttributeService } from '@core/http/attribute.service';
35 42 import { TranslateService } from '@ngx-translate/core';
36 43 import { UtilsService } from '@core/services/utils.service';
37 44 import _ from 'lodash';
  45 +import { EntityDataPageLink } from '@shared/models/query/query.models';
38 46
39 47 // @dynamic
40 48 export class MapWidgetController implements MapWidgetInterface {
... ... @@ -73,6 +81,13 @@ export class MapWidgetController implements MapWidgetInterface {
73 81 if (this.settings.draggableMarker) {
74 82 this.map.setDataSources(parseData(this.data));
75 83 }
  84 + this.pageLink = {
  85 + page: 0,
  86 + pageSize: DEFAULT_MAP_PAGE_SIZE,
  87 + textSearch: null,
  88 + dynamic: true
  89 + };
  90 + this.ctx.defaultSubscription.subscribeAllForPaginatedData(this.pageLink, null);
76 91 }
77 92
78 93 map: LeafletMap;
... ... @@ -80,6 +95,7 @@ export class MapWidgetController implements MapWidgetInterface {
80 95 schema: JsonSettingsSchema;
81 96 data: DatasourceData[];
82 97 settings: UnitedMapSettings;
  98 + pageLink: EntityDataPageLink;
83 99
84 100 public static dataKeySettingsSchema(): object {
85 101 return {};
... ... @@ -241,12 +257,7 @@ export class MapWidgetController implements MapWidgetInterface {
241 257 }
242 258
243 259 update() {
244   - if (this.drawRoutes)
245   - this.map.updatePolylines(parseArray(this.data));
246   - if (this.settings.showPolygon) {
247   - this.map.updatePolygons(parseData(this.data));
248   - }
249   - this.map.updateMarkers(parseData(this.data));
  260 + this.map.updateData(this.data, this.drawRoutes, this.settings.showPolygon);
250 261 }
251 262
252 263 resize() {
... ...
... ... @@ -161,14 +161,14 @@ export class TripAnimationComponent implements OnInit, AfterViewInit, OnDestroy
161 161 this.calcLabel();
162 162 this.calcTooltip(currentPosition.find(position => position.entityName === this.activeTrip.entityName));
163 163 if (this.mapWidget) {
164   - this.mapWidget.map.updatePolylines(this.interpolatedTimeData.map(ds => _.values(ds)), this.activeTrip);
  164 + this.mapWidget.map.updatePolylines(this.interpolatedTimeData.map(ds => _.values(ds)), true, this.activeTrip);
165 165 if (this.settings.showPolygon) {
166 166 this.mapWidget.map.updatePolygons(this.interpolatedTimeData);
167 167 }
168 168 if (this.settings.showPoints) {
169 169 this.mapWidget.map.updatePoints(_.values(_.union(this.interpolatedTimeData)[0]), this.calcTooltip);
170 170 }
171   - this.mapWidget.map.updateMarkers(currentPosition, (trip) => {
  171 + this.mapWidget.map.updateMarkers(currentPosition, true, (trip) => {
172 172 this.activeTrip = trip;
173 173 this.timeUpdated(this.currentTime)
174 174 });
... ...
... ... @@ -714,9 +714,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
714 714 }
715 715
716 716 private fetchEntityKeys(entityAliasId: string, query: string, dataKeyTypes: Array<DataKeyType>): Observable<Array<DataKey>> {
717   - return this.aliasController.getAliasInfo(entityAliasId).pipe(
718   - mergeMap((aliasInfo) => {
719   - const entity = aliasInfo.currentEntity;
  717 + return this.aliasController.resolveSingleEntityInfo(entityAliasId).pipe(
  718 + mergeMap((entity) => {
720 719 if (entity) {
721 720 const fetchEntityTasks: Array<Observable<Array<DataKey>>> = [];
722 721 for (const dataKeyType of dataKeyTypes) {
... ...