Commit 391c88e8cba1183de1d2990b524c9fcfce53736c

Authored by Igor Kulikov
1 parent e6e9be18

UI: Improve data subscription reconnect flow

@@ -37,7 +37,7 @@ import { @@ -37,7 +37,7 @@ import {
37 } from '@shared/models/telemetry/telemetry.models'; 37 } from '@shared/models/telemetry/telemetry.models';
38 import { UtilsService } from '@core/services/utils.service'; 38 import { UtilsService } from '@core/services/utils.service';
39 import { EntityDataListener, EntityDataLoadResult } from '@core/api/entity-data.service'; 39 import { EntityDataListener, EntityDataLoadResult } from '@core/api/entity-data.service';
40 -import { deepClone, isDefinedAndNotNull, isObject, objectHashCode } from '@core/utils'; 40 +import { deepClone, isDefinedAndNotNull, isEqual, isObject, objectHashCode } from '@core/utils';
41 import { PageData } from '@shared/models/page/page-data'; 41 import { PageData } from '@shared/models/page/page-data';
42 import { DataAggregator } from '@core/api/data-aggregator'; 42 import { DataAggregator } from '@core/api/data-aggregator';
43 import { NULL_UUID } from '@shared/models/id/has-uuid'; 43 import { NULL_UUID } from '@shared/models/id/has-uuid';
@@ -50,7 +50,7 @@ export interface EntityDataSubscriptionOptions { @@ -50,7 +50,7 @@ export interface EntityDataSubscriptionOptions {
50 dataKeys: Array<SubscriptionDataKey>; 50 dataKeys: Array<SubscriptionDataKey>;
51 type: widgetType; 51 type: widgetType;
52 entityFilter?: EntityFilter; 52 entityFilter?: EntityFilter;
53 - isLatestDataSubscription?: boolean; 53 + isPaginatedDataSubscription?: boolean;
54 pageLink?: EntityDataPageLink; 54 pageLink?: EntityDataPageLink;
55 keyFilters?: Array<KeyFilter>; 55 keyFilters?: Array<KeyFilter>;
56 subscriptionTimewindow?: SubscriptionTimewindow; 56 subscriptionTimewindow?: SubscriptionTimewindow;
@@ -154,7 +154,7 @@ export class EntityDataSubscription { @@ -154,7 +154,7 @@ export class EntityDataSubscription {
154 } 154 }
155 155
156 public subscribe(): Observable<EntityDataLoadResult> { 156 public subscribe(): Observable<EntityDataLoadResult> {
157 - if (!this.entityDataSubscriptionOptions.isLatestDataSubscription) { 157 + if (!this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
158 this.entityDataResolveSubject = new ReplaySubject(1); 158 this.entityDataResolveSubject = new ReplaySubject(1);
159 } else { 159 } else {
160 this.started = true; 160 this.started = true;
@@ -199,7 +199,7 @@ export class EntityDataSubscription { @@ -199,7 +199,7 @@ export class EntityDataSubscription {
199 latestValues: this.latestValues 199 latestValues: this.latestValues
200 }; 200 };
201 201
202 - if (this.entityDataSubscriptionOptions.isLatestDataSubscription) { 202 + if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
203 if (this.entityDataSubscriptionOptions.type === widgetType.latest) { 203 if (this.entityDataSubscriptionOptions.type === widgetType.latest) {
204 if (this.latestValues.length > 0) { 204 if (this.latestValues.length > 0) {
205 this.dataCommand.latestCmd = { 205 this.dataCommand.latestCmd = {
@@ -222,7 +222,7 @@ export class EntityDataSubscription { @@ -222,7 +222,7 @@ export class EntityDataSubscription {
222 ); 222 );
223 223
224 this.subscriber.reconnect$.subscribe(() => { 224 this.subscriber.reconnect$.subscribe(() => {
225 - if (this.started && !this.entityDataSubscriptionOptions.isLatestDataSubscription) { 225 + if (this.started && !this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
226 if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && 226 if (this.entityDataSubscriptionOptions.type === widgetType.timeseries &&
227 !this.history && this.tsFields.length) { 227 !this.history && this.tsFields.length) {
228 const newSubsTw: SubscriptionTimewindow = this.listener.updateRealtimeSubscription(); 228 const newSubsTw: SubscriptionTimewindow = this.listener.updateRealtimeSubscription();
@@ -271,14 +271,14 @@ export class EntityDataSubscription { @@ -271,14 +271,14 @@ export class EntityDataSubscription {
271 totalPages: 1 271 totalPages: 1
272 }; 272 };
273 this.onPageData(pageData); 273 this.onPageData(pageData);
274 - if (this.entityDataSubscriptionOptions.isLatestDataSubscription) { 274 + if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
275 if (this.entityDataSubscriptionOptions.type === widgetType.latest) { 275 if (this.entityDataSubscriptionOptions.type === widgetType.latest) {
276 this.frequency = 1000; 276 this.frequency = 1000;
277 this.timer = setTimeout(this.onTick.bind(this, true), 0); 277 this.timer = setTimeout(this.onTick.bind(this, true), 0);
278 } 278 }
279 } 279 }
280 } 280 }
281 - if (this.entityDataSubscriptionOptions.isLatestDataSubscription) { 281 + if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
282 return of(null); 282 return of(null);
283 } else { 283 } else {
284 return this.entityDataResolveSubject.asObservable(); 284 return this.entityDataResolveSubject.asObservable();
@@ -286,7 +286,7 @@ export class EntityDataSubscription { @@ -286,7 +286,7 @@ export class EntityDataSubscription {
286 } 286 }
287 287
288 public start() { 288 public start() {
289 - if (this.entityDataSubscriptionOptions.isLatestDataSubscription) { 289 + if (this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
290 return; 290 return;
291 } 291 }
292 this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow; 292 this.subsTw = this.entityDataSubscriptionOptions.subscriptionTimewindow;
@@ -432,9 +432,26 @@ export class EntityDataSubscription { @@ -432,9 +432,26 @@ export class EntityDataSubscription {
432 } 432 }
433 } 433 }
434 434
  435 + private pageDataChanged(prevPageData: PageData<EntityData>, nextPageData: PageData<EntityData>) {
  436 + const prevIds = prevPageData.data.map((entityData) => entityData.entityId.id);
  437 + const nextIds = nextPageData.data.map((entityData) => entityData.entityId.id);
  438 + return !isEqual(prevIds, nextIds);
  439 + }
  440 +
435 private onPageData(pageData: PageData<EntityData>) { 441 private onPageData(pageData: PageData<EntityData>) {
  442 + const isInitialData = !this.pageData;
  443 + if (!isInitialData && !this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
  444 + if (this.pageDataChanged(this.pageData, pageData)) {
  445 + if (this.listener.initialPageDataChanged) {
  446 + this.listener.initialPageDataChanged(pageData);
  447 + }
  448 + return;
  449 + }
  450 + }
436 this.pageData = pageData; 451 this.pageData = pageData;
437 - this.resetData(); 452 + if (isInitialData || this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
  453 + this.resetData();
  454 + }
438 const data: Array<Array<DataSetHolder>> = []; 455 const data: Array<Array<DataSetHolder>> = [];
439 for (let dataIndex = 0; dataIndex < pageData.data.length; dataIndex++) { 456 for (let dataIndex = 0; dataIndex < pageData.data.length; dataIndex++) {
440 const entityData = pageData.data[dataIndex]; 457 const entityData = pageData.data[dataIndex];
@@ -458,8 +475,10 @@ export class EntityDataSubscription { @@ -458,8 +475,10 @@ export class EntityDataSubscription {
458 ); 475 );
459 this.entityDataResolveSubject.complete(); 476 this.entityDataResolveSubject.complete();
460 } else { 477 } else {
461 - this.listener.dataLoaded(pageData, data,  
462 - this.listener.configDatasourceIndex); 478 + if (isInitialData || this.entityDataSubscriptionOptions.isPaginatedDataSubscription) {
  479 + this.listener.dataLoaded(pageData, data,
  480 + this.listener.configDatasourceIndex);
  481 + }
463 } 482 }
464 } 483 }
465 484
@@ -487,8 +506,13 @@ export class EntityDataSubscription { @@ -487,8 +506,13 @@ export class EntityDataSubscription {
487 } 506 }
488 if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && entityData.timeseries) { 507 if (this.entityDataSubscriptionOptions.type === widgetType.timeseries && entityData.timeseries) {
489 const subscriptionData = this.toSubscriptionData(entityData.timeseries, true); 508 const subscriptionData = this.toSubscriptionData(entityData.timeseries, true);
490 - if (!this.history && aggregate) {  
491 - this.dataAggregators[dataIndex].onData({data: subscriptionData}, false, false, true); 509 + if (!this.history) {
  510 + if (this.dataAggregators && this.dataAggregators[dataIndex]) {
  511 + this.dataAggregators[dataIndex].onData({data: subscriptionData}, false, false, true);
  512 + }
  513 + if (!aggregate) {
  514 + this.onData(subscriptionData, DataKeyType.timeseries, dataIndex, true, dataUpdatedCb);
  515 + }
492 } else { 516 } else {
493 this.onData(subscriptionData, DataKeyType.timeseries, dataIndex, true, dataUpdatedCb); 517 this.onData(subscriptionData, DataKeyType.timeseries, dataIndex, true, dataUpdatedCb);
494 } 518 }
@@ -33,6 +33,7 @@ export interface EntityDataListener { @@ -33,6 +33,7 @@ export interface EntityDataListener {
33 configDatasourceIndex: number; 33 configDatasourceIndex: number;
34 dataLoaded: (pageData: PageData<EntityData>, data: Array<Array<DataSetHolder>>, datasourceIndex: number) => void; 34 dataLoaded: (pageData: PageData<EntityData>, data: Array<Array<DataSetHolder>>, datasourceIndex: number) => void;
35 dataUpdated: (data: DataSetHolder, datasourceIndex: number, dataIndex: number, dataKeyIndex: number, detectChanges: boolean) => void; 35 dataUpdated: (data: DataSetHolder, datasourceIndex: number, dataIndex: number, dataKeyIndex: number, detectChanges: boolean) => void;
  36 + initialPageDataChanged?: (nextPageData: PageData<EntityData>) => void;
36 updateRealtimeSubscription?: () => SubscriptionTimewindow; 37 updateRealtimeSubscription?: () => SubscriptionTimewindow;
37 setRealtimeSubscription?: (subscriptionTimewindow: SubscriptionTimewindow) => void; 38 setRealtimeSubscription?: (subscriptionTimewindow: SubscriptionTimewindow) => void;
38 subscription?: EntityDataSubscription; 39 subscription?: EntityDataSubscription;
@@ -70,9 +71,9 @@ export class EntityDataService { @@ -70,9 +71,9 @@ export class EntityDataService {
70 listener.subscription.start(); 71 listener.subscription.start();
71 } 72 }
72 73
73 - public subscribeForLatestData(listener: EntityDataListener,  
74 - pageLink: EntityDataPageLink,  
75 - keyFilters: KeyFilter[]) { 74 + public subscribeForPaginatedData(listener: EntityDataListener,
  75 + pageLink: EntityDataPageLink,
  76 + keyFilters: KeyFilter[]) {
76 const datasource = listener.configDatasource; 77 const datasource = listener.configDatasource;
77 if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !pageLink)) { 78 if (datasource.type === DatasourceType.entity && (!datasource.entityFilter || !pageLink)) {
78 return; 79 return;
@@ -89,7 +90,7 @@ export class EntityDataService { @@ -89,7 +90,7 @@ export class EntityDataService {
89 private createSubscription(listener: EntityDataListener, 90 private createSubscription(listener: EntityDataListener,
90 pageLink: EntityDataPageLink, 91 pageLink: EntityDataPageLink,
91 keyFilters: KeyFilter[], 92 keyFilters: KeyFilter[],
92 - isLatestDataSubscription: boolean): EntityDataSubscription { 93 + isPaginatedDataSubscription: boolean): EntityDataSubscription {
93 const datasource = listener.configDatasource; 94 const datasource = listener.configDatasource;
94 const subscriptionDataKeys: Array<SubscriptionDataKey> = []; 95 const subscriptionDataKeys: Array<SubscriptionDataKey> = [];
95 datasource.dataKeys.forEach((dataKey) => { 96 datasource.dataKeys.forEach((dataKey) => {
@@ -111,7 +112,7 @@ export class EntityDataService { @@ -111,7 +112,7 @@ export class EntityDataService {
111 entityDataSubscriptionOptions.pageLink = pageLink; 112 entityDataSubscriptionOptions.pageLink = pageLink;
112 entityDataSubscriptionOptions.keyFilters = keyFilters; 113 entityDataSubscriptionOptions.keyFilters = keyFilters;
113 } 114 }
114 - entityDataSubscriptionOptions.isLatestDataSubscription = isLatestDataSubscription; 115 + entityDataSubscriptionOptions.isPaginatedDataSubscription = isPaginatedDataSubscription;
115 return new EntityDataSubscription(entityDataSubscriptionOptions, 116 return new EntityDataSubscription(entityDataSubscriptionOptions,
116 listener, this.telemetryService, this.utils); 117 listener, this.telemetryService, this.utils);
117 } 118 }
@@ -41,7 +41,7 @@ import { EntityAliases } from '@shared/models/alias.models'; @@ -41,7 +41,7 @@ import { EntityAliases } from '@shared/models/alias.models';
41 import { EntityInfo } from '@app/shared/models/entity.models'; 41 import { EntityInfo } from '@app/shared/models/entity.models';
42 import { IDashboardComponent } from '@home/models/dashboard-component.models'; 42 import { IDashboardComponent } from '@home/models/dashboard-component.models';
43 import * as moment_ from 'moment'; 43 import * as moment_ from 'moment';
44 -import { EntityDataPageLink, EntityFilter, KeyFilter } from '@shared/models/query/query.models'; 44 +import { EntityData, EntityDataPageLink, EntityFilter, KeyFilter } from '@shared/models/query/query.models';
45 import { EntityDataService } from '@core/api/entity-data.service'; 45 import { EntityDataService } from '@core/api/entity-data.service';
46 import { PageData } from '@shared/models/page/page-data'; 46 import { PageData } from '@shared/models/page/page-data';
47 47
@@ -185,6 +185,7 @@ export class WidgetSubscriptionContext { @@ -185,6 +185,7 @@ export class WidgetSubscriptionContext {
185 export interface WidgetSubscriptionCallbacks { 185 export interface WidgetSubscriptionCallbacks {
186 onDataUpdated?: (subscription: IWidgetSubscription, detectChanges: boolean) => void; 186 onDataUpdated?: (subscription: IWidgetSubscription, detectChanges: boolean) => void;
187 onDataUpdateError?: (subscription: IWidgetSubscription, e: any) => void; 187 onDataUpdateError?: (subscription: IWidgetSubscription, e: any) => void;
  188 + onInitialPageDataChanged?: (subscription: IWidgetSubscription, nextPageData: PageData<EntityData>) => void;
188 dataLoading?: (subscription: IWidgetSubscription) => void; 189 dataLoading?: (subscription: IWidgetSubscription) => void;
189 legendDataUpdated?: (subscription: IWidgetSubscription, detectChanges: boolean) => void; 190 legendDataUpdated?: (subscription: IWidgetSubscription, detectChanges: boolean) => void;
190 timeWindowUpdated?: (subscription: IWidgetSubscription, timeWindowConfig: Timewindow) => void; 191 timeWindowUpdated?: (subscription: IWidgetSubscription, timeWindowConfig: Timewindow) => void;
@@ -279,9 +280,9 @@ export interface IWidgetSubscription { @@ -279,9 +280,9 @@ export interface IWidgetSubscription {
279 280
280 subscribe(): void; 281 subscribe(): void;
281 282
282 - subscribeForLatestData(datasourceIndex: number,  
283 - pageLink: EntityDataPageLink,  
284 - keyFilters: KeyFilter[]): void; 283 + subscribeForPaginatedData(datasourceIndex: number,
  284 + pageLink: EntityDataPageLink,
  285 + keyFilters: KeyFilter[]): void;
285 286
286 isDataResolved(): boolean; 287 isDataResolved(): boolean;
287 288
@@ -209,6 +209,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -209,6 +209,7 @@ export class WidgetSubscription implements IWidgetSubscription {
209 } else { 209 } else {
210 this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || (() => {}); 210 this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || (() => {});
211 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || (() => {}); 211 this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || (() => {});
  212 + this.callbacks.onInitialPageDataChanged = this.callbacks.onInitialPageDataChanged || (() => {});
212 this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {}); 213 this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {});
213 this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || (() => {}); 214 this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || (() => {});
214 this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {}); 215 this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {});
@@ -400,6 +401,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -400,6 +401,7 @@ export class WidgetSubscription implements IWidgetSubscription {
400 dataLoaded: (pageData, data1, datasourceIndex) => { 401 dataLoaded: (pageData, data1, datasourceIndex) => {
401 this.dataLoaded(pageData, data1, datasourceIndex, true) 402 this.dataLoaded(pageData, data1, datasourceIndex, true)
402 }, 403 },
  404 + initialPageDataChanged: this.initialPageDataChanged.bind(this),
403 dataUpdated: this.dataUpdated.bind(this), 405 dataUpdated: this.dataUpdated.bind(this),
404 updateRealtimeSubscription: () => { 406 updateRealtimeSubscription: () => {
405 this.subscriptionTimewindow = this.updateRealtimeSubscription(); 407 this.subscriptionTimewindow = this.updateRealtimeSubscription();
@@ -852,9 +854,9 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -852,9 +854,9 @@ export class WidgetSubscription implements IWidgetSubscription {
852 } 854 }
853 } 855 }
854 856
855 - subscribeForLatestData(datasourceIndex: number,  
856 - pageLink: EntityDataPageLink,  
857 - keyFilters: KeyFilter[]): void { 857 + subscribeForPaginatedData(datasourceIndex: number,
  858 + pageLink: EntityDataPageLink,
  859 + keyFilters: KeyFilter[]): void {
858 let entityDataListener = this.entityDataListeners[datasourceIndex]; 860 let entityDataListener = this.entityDataListeners[datasourceIndex];
859 if (entityDataListener) { 861 if (entityDataListener) {
860 this.ctx.entityDataService.stopSubscription(entityDataListener); 862 this.ctx.entityDataService.stopSubscription(entityDataListener);
@@ -871,7 +873,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -871,7 +873,7 @@ export class WidgetSubscription implements IWidgetSubscription {
871 dataUpdated: this.dataUpdated.bind(this) 873 dataUpdated: this.dataUpdated.bind(this)
872 }; 874 };
873 this.entityDataListeners[datasourceIndex] = entityDataListener; 875 this.entityDataListeners[datasourceIndex] = entityDataListener;
874 - this.ctx.entityDataService.subscribeForLatestData(entityDataListener, pageLink, keyFilters); 876 + this.ctx.entityDataService.subscribeForPaginatedData(entityDataListener, pageLink, keyFilters);
875 } 877 }
876 } 878 }
877 879
@@ -1156,6 +1158,10 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1156,6 +1158,10 @@ export class WidgetSubscription implements IWidgetSubscription {
1156 return this.timewindowForComparison; 1158 return this.timewindowForComparison;
1157 } 1159 }
1158 1160
  1161 + private initialPageDataChanged(nextPageData: PageData<EntityData>) {
  1162 + this.callbacks.onInitialPageDataChanged(this, nextPageData);
  1163 + }
  1164 +
1159 private dataLoaded(pageData: PageData<EntityData>, 1165 private dataLoaded(pageData: PageData<EntityData>,
1160 data: Array<Array<DataSetHolder>>, 1166 data: Array<Array<DataSetHolder>>,
1161 datasourceIndex: number, isUpdate: boolean) { 1167 datasourceIndex: number, isUpdate: boolean) {
@@ -614,7 +614,7 @@ class EntityDatasource implements DataSource<EntityData> { @@ -614,7 +614,7 @@ class EntityDatasource implements DataSource<EntityData> {
614 } 614 }
615 615
616 loadEntities(pageLink: EntityDataPageLink, keyFilters: KeyFilter[]) { 616 loadEntities(pageLink: EntityDataPageLink, keyFilters: KeyFilter[]) {
617 - this.subscription.subscribeForLatestData(0, pageLink, keyFilters); 617 + this.subscription.subscribeForPaginatedData(0, pageLink, keyFilters);
618 /* this.fetchEntities(pageLink).pipe( 618 /* this.fetchEntities(pageLink).pipe(
619 catchError(() => of(emptyPageData<EntityData>())), 619 catchError(() => of(emptyPageData<EntityData>())),
620 ).subscribe( 620 ).subscribe(
@@ -818,6 +818,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @@ -818,6 +818,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
818 onDataUpdateError: (subscription, e) => { 818 onDataUpdateError: (subscription, e) => {
819 this.handleWidgetException(e); 819 this.handleWidgetException(e);
820 }, 820 },
  821 + onInitialPageDataChanged: (subscription, nextPageData) => {
  822 + this.reInit();
  823 + },
821 dataLoading: (subscription) => { 824 dataLoading: (subscription) => {
822 if (this.loadingData !== subscription.loadingData) { 825 if (this.loadingData !== subscription.loadingData) {
823 this.loadingData = subscription.loadingData; 826 this.loadingData = subscription.loadingData;