Browse Source

UI: Alarm data query support

pull/3068/head
Igor Kulikov 6 years ago
parent
commit
4747e110bc
  1. 2
      application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java
  2. 5
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUpdate.java
  3. 1
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdate.java
  4. 21
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdateType.java
  5. 5
      application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java
  6. 176
      ui-ngx/src/app/core/api/alarm-data-subscription.ts
  7. 94
      ui-ngx/src/app/core/api/alarm-data.service.ts
  8. 17
      ui-ngx/src/app/core/api/widget-api.models.ts
  9. 205
      ui-ngx/src/app/core/api/widget-subscription.ts
  10. 15
      ui-ngx/src/app/core/ws/telemetry-websocket.service.ts
  11. 7
      ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html
  12. 243
      ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts
  13. 15
      ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts
  14. 17
      ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts
  15. 9
      ui-ngx/src/app/modules/home/components/widget/widget.component.ts
  16. 4
      ui-ngx/src/app/shared/models/alarm.models.ts
  17. 56
      ui-ngx/src/app/shared/models/query/query.models.ts
  18. 96
      ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts

2
application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java

@ -256,7 +256,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
} else {
entitiesSortOrder = sortOrder;
}
EntityDataPageLink edpl = new EntityDataPageLink(0, maxEntitiesPerAlarmSubscription, null, entitiesSortOrder);
EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null, entitiesSortOrder);
EntityDataQuery edq = new EntityDataQuery(adq.getEntityFilter(), edpl, adq.getEntityFields(), adq.getLatestValues(), adq.getKeyFilters());
PageData<EntityData> entitiesData = entityService.findEntityDataByQuery(ctx.getTenantId(), ctx.getCustomerId(), edq);
List<EntityData> entities = entitiesData.getData();

5
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/AlarmDataUpdate.java

@ -35,6 +35,11 @@ public class AlarmDataUpdate extends DataUpdate<AlarmData> {
super(cmdId, null, null, errorCode, errorMsg);
}
@Override
public DataUpdateType getDataUpdateType() {
return DataUpdateType.ALARM_DATA;
}
@JsonCreator
public AlarmDataUpdate(@JsonProperty("cmdId") int cmdId,
@JsonProperty("data") PageData<AlarmData> data,

1
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdate.java

@ -40,4 +40,5 @@ public abstract class DataUpdate<T> {
this(cmdId, null, null, errorCode, errorMsg);
}
public abstract DataUpdateType getDataUpdateType();
}

21
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdateType.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.service.telemetry.cmd.v2;
public enum DataUpdateType {
ENTITY_DATA,
ALARM_DATA
}

5
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/EntityDataUpdate.java

@ -33,6 +33,11 @@ public class EntityDataUpdate extends DataUpdate<EntityData> {
super(cmdId, null, null, errorCode, errorMsg);
}
@Override
public DataUpdateType getDataUpdateType() {
return DataUpdateType.ENTITY_DATA;
}
@JsonCreator
public EntityDataUpdate(@JsonProperty("cmdId") int cmdId,
@JsonProperty("data") PageData<EntityData> data,

176
ui-ngx/src/app/core/api/alarm-data-subscription.ts

@ -0,0 +1,176 @@
///
/// 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.
///
import {
AlarmDataCmd,
DataKeyType,
TelemetryService,
TelemetrySubscriber
} from '@shared/models/telemetry/telemetry.models';
import { DatasourceType } from '@shared/models/widget.models';
import {
AlarmData,
AlarmDataPageLink,
EntityFilter,
EntityKey,
EntityKeyType,
KeyFilter
} 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';
export interface AlarmSubscriptionDataKey {
name: string;
type: DataKeyType;
}
export interface AlarmDataSubscriptionOptions {
datasourceType: DatasourceType;
dataKeys: Array<AlarmSubscriptionDataKey>;
entityFilter?: EntityFilter;
pageLink?: AlarmDataPageLink;
keyFilters?: Array<KeyFilter>;
additionalKeyFilters?: Array<KeyFilter>;
subscriptionTimewindow?: SubscriptionTimewindow;
}
export class AlarmDataSubscription {
private datasourceType: DatasourceType = this.alarmDataSubscriptionOptions.datasourceType;
private history: boolean;
private realtime: boolean;
private subscriber: TelemetrySubscriber;
private alarmDataCommand: AlarmDataCmd;
private pageData: PageData<AlarmData>;
private alarmIdToDataIndex: {[id: string]: number};
private subsTw: SubscriptionTimewindow;
constructor(public alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions,
private listener: AlarmDataListener,
private telemetryService: TelemetryService,
private utils: UtilsService) {
}
public unsubscribe() {
if (this.datasourceType === DatasourceType.entity) {
if (this.subscriber) {
this.subscriber.unsubscribe();
this.subscriber = null;
}
}
}
public subscribe() {
this.subsTw = this.alarmDataSubscriptionOptions.subscriptionTimewindow;
this.history = this.alarmDataSubscriptionOptions.subscriptionTimewindow &&
isObject(this.alarmDataSubscriptionOptions.subscriptionTimewindow.fixedWindow);
this.realtime = this.alarmDataSubscriptionOptions.subscriptionTimewindow &&
isDefinedAndNotNull(this.alarmDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs);
if (this.datasourceType === DatasourceType.entity) {
this.subscriber = new TelemetrySubscriber(this.telemetryService);
this.alarmDataCommand = new AlarmDataCmd();
const entityFields: Array<EntityKey> =
this.alarmDataSubscriptionOptions.dataKeys.filter(dataKey => dataKey.type === DataKeyType.entityField).map(
dataKey => ({ type: EntityKeyType.ENTITY_FIELD, key: dataKey.name })
);
const attrFields = this.alarmDataSubscriptionOptions.dataKeys.filter(dataKey => dataKey.type === DataKeyType.attribute).map(
dataKey => ({ type: EntityKeyType.ATTRIBUTE, key: dataKey.name })
);
const tsFields = this.alarmDataSubscriptionOptions.dataKeys.filter(dataKey => dataKey.type === DataKeyType.timeseries).map(
dataKey => ({ type: EntityKeyType.TIME_SERIES, key: dataKey.name })
);
const latestValues = attrFields.concat(tsFields);
let keyFilters = this.alarmDataSubscriptionOptions.keyFilters;
if (this.alarmDataSubscriptionOptions.additionalKeyFilters) {
if (keyFilters) {
keyFilters = keyFilters.concat(this.alarmDataSubscriptionOptions.additionalKeyFilters);
} else {
keyFilters = this.alarmDataSubscriptionOptions.additionalKeyFilters;
}
}
this.alarmDataCommand.query = {
entityFilter: this.alarmDataSubscriptionOptions.entityFilter,
pageLink: deepClone(this.alarmDataSubscriptionOptions.pageLink),
keyFilters,
entityFields,
latestValues
};
if (this.history) {
this.alarmDataCommand.query.pageLink.startTs = this.subsTw.fixedWindow.startTimeMs;
this.alarmDataCommand.query.pageLink.endTs = this.subsTw.fixedWindow.endTimeMs;
} else {
this.alarmDataCommand.query.pageLink.timeWindow = this.subsTw.realtimeWindowMs;
}
this.subscriber.subscriptionCommands.push(this.alarmDataCommand);
this.subscriber.alarmData$.subscribe((alarmDataUpdate) => {
if (alarmDataUpdate.data) {
this.onPageData(alarmDataUpdate.data);
} else if (alarmDataUpdate.update) {
this.onDataUpdate(alarmDataUpdate.update);
}
});
this.subscriber.subscribe();
} else if (this.datasourceType === DatasourceType.function) {
const pageData: PageData<AlarmData> = {
data: [{...simulatedAlarm, entityId: '1', latest: {}}],
hasNext: false,
totalElements: 1,
totalPages: 1
};
this.onPageData(pageData);
}
}
private resetData() {
this.alarmIdToDataIndex = {};
for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) {
const alarmData = this.pageData.data[dataIndex];
this.alarmIdToDataIndex[alarmData.id.id] = dataIndex;
}
}
private onPageData(pageData: PageData<AlarmData>) {
this.pageData = pageData;
this.resetData();
this.listener.alarmsLoaded(pageData, this.alarmDataSubscriptionOptions.pageLink);
}
private onDataUpdate(update: Array<AlarmData>) {
for (const alarmData of update) {
const dataIndex = this.alarmIdToDataIndex[alarmData.id.id];
if (isDefined(dataIndex) && dataIndex >= 0) {
this.pageData.data[dataIndex] = alarmData;
}
}
this.listener.alarmsUpdated(update, this.pageData);
}
}

94
ui-ngx/src/app/core/api/alarm-data.service.ts

@ -0,0 +1,94 @@
///
/// 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.
///
import { SubscriptionTimewindow } from '@shared/models/time/time.models';
import { Datasource, DatasourceType } from '@shared/models/widget.models';
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,
AlarmSubscriptionDataKey
} from '@core/api/alarm-data-subscription';
import { deepClone } from '@core/utils';
export interface AlarmDataListener {
subscriptionTimewindow?: SubscriptionTimewindow;
alarmSource: Datasource;
alarmsLoaded: (pageData: PageData<AlarmData>, pageLink: AlarmDataPageLink) => void;
alarmsUpdated: (update: Array<AlarmData>, pageData: PageData<AlarmData>) => void;
subscription?: AlarmDataSubscription;
}
@Injectable({
providedIn: 'root'
})
export class AlarmDataService {
constructor(private telemetryService: TelemetryWebsocketService,
private utils: UtilsService) {}
public subscribeForAlarms(listener: AlarmDataListener,
pageLink: AlarmDataPageLink,
keyFilters: KeyFilter[]) {
const alarmSource = listener.alarmSource;
if (alarmSource.type === DatasourceType.entity && (!alarmSource.entityFilter || !pageLink)) {
return;
}
listener.subscription = this.createSubscription(listener,
pageLink, alarmSource.keyFilters, keyFilters);
return listener.subscription.subscribe();
}
public stopSubscription(listener: AlarmDataListener) {
if (listener.subscription) {
listener.subscription.unsubscribe();
}
}
private createSubscription(listener: AlarmDataListener,
pageLink: AlarmDataPageLink,
keyFilters: KeyFilter[],
additionalKeyFilters: KeyFilter[]): AlarmDataSubscription {
const alarmSource = listener.alarmSource;
const alarmSubscriptionDataKeys: Array<AlarmSubscriptionDataKey> = [];
alarmSource.dataKeys.forEach((dataKey) => {
const alarmSubscriptionDataKey: AlarmSubscriptionDataKey = {
name: dataKey.name,
type: dataKey.type
};
alarmSubscriptionDataKeys.push(alarmSubscriptionDataKey);
});
const alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions = {
datasourceType: alarmSource.type,
dataKeys: alarmSubscriptionDataKeys,
subscriptionTimewindow: deepClone(listener.subscriptionTimewindow)
};
if (alarmDataSubscriptionOptions.datasourceType === DatasourceType.entity) {
alarmDataSubscriptionOptions.entityFilter = alarmSource.entityFilter;
alarmDataSubscriptionOptions.pageLink = pageLink;
alarmDataSubscriptionOptions.keyFilters = keyFilters;
alarmDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters;
}
return new AlarmDataSubscription(alarmDataSubscriptionOptions,
listener, this.telemetryService, this.utils);
}
}

17
ui-ngx/src/app/core/api/widget-api.models.ts

@ -41,6 +41,7 @@ import { EntityInfo } from '@app/shared/models/entity.models';
import { IDashboardComponent } from '@home/models/dashboard-component.models';
import * as moment_ from 'moment';
import {
AlarmData, AlarmDataPageLink,
EntityData,
EntityDataPageLink,
EntityFilter,
@ -51,6 +52,7 @@ import {
import { EntityDataService } from '@core/api/entity-data.service';
import { PageData } from '@shared/models/page/page-data';
import { TranslateService } from '@ngx-translate/core';
import { AlarmDataService } from '@core/api/alarm-data.service';
export interface TimewindowFunctions {
onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void;
@ -184,9 +186,9 @@ export class WidgetSubscriptionContext {
timeService: TimeService;
deviceService: DeviceService;
alarmService: AlarmService;
translate: TranslateService;
entityDataService: EntityDataService;
alarmDataService: AlarmDataService;
utils: UtilsService;
raf: RafService;
widgetUtils: IWidgetUtils;
@ -218,10 +220,10 @@ export interface WidgetSubscriptionOptions {
type?: widgetType;
stateData?: boolean;
alarmSource?: Datasource;
alarmSearchStatus?: AlarmSearchStatus;
/* alarmSearchStatus?: AlarmSearchStatus;
alarmsPollingInterval?: number;
alarmsMaxCountLoad?: number;
alarmsFetchSize?: number;
alarmsFetchSize?: number; */
datasources?: Array<Datasource>;
hasDataPageLink?: boolean;
singleEntity?: boolean;
@ -269,10 +271,10 @@ export interface IWidgetSubscription {
timeWindow?: WidgetTimewindow;
comparisonTimeWindow?: WidgetTimewindow;
alarms?: Array<AlarmInfo>;
alarms?: PageData<AlarmData>;
alarmSource?: Datasource;
alarmSearchStatus?: AlarmSearchStatus;
alarmsPollingInterval?: number;
/* alarmSearchStatus?: AlarmSearchStatus;
alarmsPollingInterval?: number; */
targetDeviceAliasIds?: Array<string>;
targetDeviceIds?: Array<string>;
@ -309,6 +311,9 @@ export interface IWidgetSubscription {
pageLink: EntityDataPageLink,
keyFilters: KeyFilter[]): Observable<any>;
subscribeForAlarms(pageLink: AlarmDataPageLink,
keyFilters: KeyFilter[]): void;
isDataResolved(): boolean;
destroy(): void;

205
ui-ngx/src/app/core/api/widget-subscription.ts

@ -47,21 +47,23 @@ import {
import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';
import { CancelAnimationFrame } from '@core/services/raf.service';
import { EntityType } from '@shared/models/entity-type.models';
import { AlarmInfo, AlarmSearchStatus } from '@shared/models/alarm.models';
import { createLabelFromDatasource, deepClone, isDefined, isEqual } from '@core/utils';
import { AlarmSourceListener } from '@core/http/alarm.service';
import { EntityId } from '@app/shared/models/id/entity-id';
import * as moment_ from 'moment';
import { PageData } from '@shared/models/page/page-data';
import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { EntityDataListener } from '@core/api/entity-data.service';
import {
AlarmData,
AlarmDataPageLink,
EntityData,
EntityDataPageLink,
entityDataToEntityInfo,
EntityKeyType,
KeyFilter,
updateDatasourceFromEntityInfo
} from '@shared/models/query/query.models';
import { map } from 'rxjs/operators';
import { AlarmDataListener } from '@core/api/alarm-data.service';
const moment = moment_;
@ -102,10 +104,11 @@ export class WidgetSubscription implements IWidgetSubscription {
comparisonTimeWindow: WidgetTimewindow;
timewindowForComparison: SubscriptionTimewindow;
alarms: Array<AlarmInfo>;
// alarms: Array<AlarmInfo>;
alarms: PageData<AlarmData>;
alarmSource: Datasource;
private alarmSearchStatusValue: AlarmSearchStatus;
/* private alarmSearchStatusValue: AlarmSearchStatus;
set alarmSearchStatus(value: AlarmSearchStatus) {
if (this.alarmSearchStatusValue !== value) {
@ -116,12 +119,14 @@ export class WidgetSubscription implements IWidgetSubscription {
get alarmSearchStatus(): AlarmSearchStatus {
return this.alarmSearchStatusValue;
}
}*/
alarmDataListener: AlarmDataListener;
alarmsPollingInterval: number;
/* alarmsPollingInterval: number;
alarmsMaxCountLoad: number;
alarmsFetchSize: number;
alarmSourceListener: AlarmSourceListener;
alarmSourceListener: AlarmSourceListener;*/
loadingData: boolean;
@ -181,7 +186,7 @@ export class WidgetSubscription implements IWidgetSubscription {
this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {});
this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {});
this.alarmSource = options.alarmSource;
this.alarmSearchStatusValue = isDefined(options.alarmSearchStatus) ?
/*this.alarmSearchStatusValue = isDefined(options.alarmSearchStatus) ?
options.alarmSearchStatus : AlarmSearchStatus.ANY;
this.alarmsPollingInterval = isDefined(options.alarmsPollingInterval) ?
options.alarmsPollingInterval : 5000;
@ -189,8 +194,10 @@ export class WidgetSubscription implements IWidgetSubscription {
options.alarmsMaxCountLoad : 0;
this.alarmsFetchSize = isDefined(options.alarmsFetchSize) ?
options.alarmsFetchSize : 100;
this.alarmSourceListener = null;
this.alarms = [];
this.alarmSourceListener = null;*/
this.alarmDataListener = null;
// this.alarms = [];
this.alarms = emptyPageData();
this.originalTimewindow = null;
this.timeWindow = {};
this.useDashboardTimewindow = options.useDashboardTimewindow;
@ -290,7 +297,7 @@ export class WidgetSubscription implements IWidgetSubscription {
if (this.targetDeviceId) {
this.rpcEnabled = true;
} else {
this.rpcEnabled = this.ctx.utils.widgetEditMode ? true : false;
this.rpcEnabled = this.ctx.utils.widgetEditMode;
}
this.hasResolvedData = this.rpcEnabled;
this.callbacks.rpcStateChanged(this);
@ -317,7 +324,7 @@ export class WidgetSubscription implements IWidgetSubscription {
if (this.targetDeviceId) {
this.rpcEnabled = true;
} else {
this.rpcEnabled = this.ctx.utils.widgetEditMode ? true : false;
this.rpcEnabled = this.ctx.utils.widgetEditMode;
}
this.hasResolvedData = true;
this.callbacks.rpcStateChanged(this);
@ -356,6 +363,7 @@ export class WidgetSubscription implements IWidgetSubscription {
}
private configureAlarmsData() {
this.notifyDataLoaded();
}
private initDataSubscription(): Observable<any> {
@ -482,13 +490,17 @@ export class WidgetSubscription implements IWidgetSubscription {
entityName = this.targetDeviceName;
}
} else if (this.type === widgetType.alarm) {
if (this.alarmSource && this.alarmSource.entityType && this.alarmSource.entityId) {
entityId = {
entityType: this.alarmSource.entityType,
id: this.alarmSource.entityId
};
entityName = this.alarmSource.entityName;
entityLabel = this.alarmSource.entityLabel;
if (this.alarms && this.alarms.data.length) {
const data = this.alarms.data[0];
entityId = data.originator;
entityName = data.originatorName;
if (data.latest && data.latest[EntityKeyType.ENTITY_FIELD]) {
const entityFields = data.latest[EntityKeyType.ENTITY_FIELD];
const labelValue = entityFields.label;
if (labelValue) {
entityLabel = labelValue.value;
}
}
}
} else {
for (const datasource of this.datasources) {
@ -522,7 +534,6 @@ export class WidgetSubscription implements IWidgetSubscription {
} else {
return this.checkSubscriptions(aliasIds);
}
return false;
}
onFiltersChanged(filterIds: Array<string>): boolean {
@ -573,12 +584,6 @@ export class WidgetSubscription implements IWidgetSubscription {
return false;
}
private onAlarmSearchStatusChanged() {
if (this.type === widgetType.alarm) {
this.update();
}
}
updateDataVisibility(index: number): void {
if (this.displayLegend) {
const hidden = this.legendData.keys[index].dataKey.hidden;
@ -752,11 +757,12 @@ export class WidgetSubscription implements IWidgetSubscription {
}
update() {
if (this.type === widgetType.rpc || this.type === widgetType.alarm) {
this.unsubscribe();
this.subscribe();
} else {
this.dataSubscribe();
if (this.type !== widgetType.rpc) {
if (this.type === widgetType.alarm) {
this.updateAlarmDataSubscription();
} else {
this.dataSubscribe();
}
}
}
@ -821,13 +827,41 @@ export class WidgetSubscription implements IWidgetSubscription {
}
}
private doSubscribe() {
if (this.type === widgetType.rpc) {
return;
subscribeForAlarms(pageLink: AlarmDataPageLink,
keyFilters: KeyFilter[]) {
if (this.alarmDataListener) {
this.ctx.alarmDataService.stopSubscription(this.alarmDataListener);
}
if (this.type === widgetType.alarm) {
this.alarmsSubscribe();
} else {
if (this.timeWindowConfig) {
this.updateRealtimeSubscription();
if (this.subscriptionTimewindow.fixedWindow) {
this.onDataUpdated();
}
}
this.alarmDataListener = {
subscriptionTimewindow: this.subscriptionTimewindow,
alarmSource: this.alarmSource,
alarmsLoaded: this.alarmsLoaded.bind(this),
alarmsUpdated: this.alarmsUpdated.bind(this)
};
this.alarms = emptyPageData();
this.ctx.alarmDataService.subscribeForAlarms(this.alarmDataListener, pageLink, keyFilters);
let forceUpdate = false;
if (this.alarmSource.unresolvedStateEntity ||
(this.alarmSource.type === DatasourceType.entity && !this.alarmSource.entityId)
) {
forceUpdate = true;
}
if (forceUpdate) {
this.onDataUpdated();
}
}
private doSubscribe() {
if (this.type !== widgetType.rpc && this.type !== widgetType.alarm) {
this.dataSubscribe();
}
}
@ -858,7 +892,7 @@ export class WidgetSubscription implements IWidgetSubscription {
}
}
private alarmsSubscribe() {
/* private alarmsSubscribe() {
this.notifyDataLoading();
if (this.timeWindowConfig) {
this.updateRealtimeSubscription();
@ -875,9 +909,10 @@ export class WidgetSubscription implements IWidgetSubscription {
alarmsFetchSize: this.alarmsFetchSize,
alarmsUpdated: alarms => this.alarmsUpdated(alarms)
};
this.alarms = null;
this.ctx.alarmService.subscribeForAlarms(this.alarmSourceListener);
this.alarms = emptyPageData();
this.ctx.alarmDataService.subscribeForAlarms(this.alarmDataListener);
let forceUpdate = false;
if (this.alarmSource.unresolvedStateEntity ||
@ -889,7 +924,7 @@ export class WidgetSubscription implements IWidgetSubscription {
this.notifyDataLoaded();
this.onDataUpdated();
}
}
} */
unsubscribe() {
@ -910,33 +945,62 @@ export class WidgetSubscription implements IWidgetSubscription {
}
private alarmsUnsubscribe() {
if (this.alarmSourceListener) {
this.ctx.alarmService.unsubscribeFromAlarms(this.alarmSourceListener);
this.alarmSourceListener = null;
if (this.alarmDataListener) {
this.ctx.alarmDataService.stopSubscription(this.alarmDataListener);
this.alarmDataListener = null;
}
}
private checkRpcTarget(aliasIds: Array<string>): boolean {
if (aliasIds.indexOf(this.targetDeviceAliasId) > -1) {
return true;
} else {
return false;
}
return aliasIds.indexOf(this.targetDeviceAliasId) > -1;
}
private checkAlarmSource(aliasIds: Array<string>): boolean {
if (this.options.alarmSource && this.options.alarmSource.entityAliasId) {
return aliasIds.indexOf(this.options.alarmSource.entityAliasId) > -1;
} else {
return false;
if (aliasIds.indexOf(this.options.alarmSource.entityAliasId) > -1) {
this.updateAlarmSubscription();
}
}
return false;
}
private checkAlarmSourceFilters(filterIds: Array<string>): boolean {
if (this.options.alarmSource && this.options.alarmSource.filterId) {
return filterIds.indexOf(this.options.alarmSource.filterId) > -1;
if (filterIds.indexOf(this.options.alarmSource.filterId) > -1) {
this.updateAlarmSubscription();
}
}
return false;
}
private updateAlarmSubscription() {
this.alarmSource = this.options.alarmSource;
if (!this.ctx.aliasController) {
this.hasResolvedData = true;
this.configureAlarmsData();
this.updateAlarmDataSubscription();
} else {
return false;
this.ctx.aliasController.resolveAlarmSource(this.alarmSource).subscribe(
(alarmSource) => {
this.alarmSource = alarmSource;
if (alarmSource) {
this.hasResolvedData = true;
}
this.configureAlarmsData();
this.updateAlarmDataSubscription();
},
() => {
this.notifyDataLoaded();
}
);
}
}
private updateAlarmDataSubscription() {
if (this.alarmDataListener) {
const pageLink = this.alarmDataListener.subscription.alarmDataSubscriptionOptions.pageLink;
const keyFilters = this.alarmDataListener.subscription.alarmDataSubscriptionOptions.additionalKeyFilters;
this.subscribeForAlarms(pageLink, keyFilters);
}
}
@ -999,7 +1063,7 @@ export class WidgetSubscription implements IWidgetSubscription {
}
);
},
(err) => {
() => {
this.notifyDataLoaded();
}
);
@ -1031,11 +1095,6 @@ export class WidgetSubscription implements IWidgetSubscription {
}
}
private notifyDataLoading() {
this.loadingData = true;
this.callbacks.dataLoading(this);
}
private notifyDataLoaded() {
this.loadingData = false;
this.callbacks.dataLoading(this);
@ -1100,23 +1159,21 @@ export class WidgetSubscription implements IWidgetSubscription {
const datasources = pageData.data.map((entityData, index) =>
this.entityDataToDatasource(datasource, entityData, index)
);
const datasourcesPage: PageData<Datasource> = {
this.datasourcePages[datasourceIndex] = {
data: datasources,
hasNext: pageData.hasNext,
totalElements: pageData.totalElements,
totalPages: pageData.totalPages
};
this.datasourcePages[datasourceIndex] = datasourcesPage;
const datasourceData = datasources.map((datasourceElement, index) =>
this.entityDataToDatasourceData(datasourceElement, data[index])
);
const datasourceDataPage: PageData<Array<DatasourceData>> = {
this.dataPages[datasourceIndex] = {
data: datasourceData,
hasNext: pageData.hasNext,
totalElements: pageData.totalElements,
totalPages: pageData.totalPages
};
this.dataPages[datasourceIndex] = datasourceDataPage;
if (datasource.type === DatasourceType.entity &&
pageData.hasNext && pageLink.pageSize > 1) {
if (this.warnOnPageDataOverflow) {
@ -1215,8 +1272,8 @@ export class WidgetSubscription implements IWidgetSubscription {
private entityDataToDatasourceData(datasource: Datasource, data: Array<DataSetHolder>): Array<DatasourceData> {
return datasource.dataKeys.map((dataKey, keyIndex) => {
dataKey.hidden = dataKey.settings.hideDataByDefault ? true : false;
dataKey.inLegend = dataKey.settings.removeFromLegend ? false : true;
dataKey.hidden = !!dataKey.settings.hideDataByDefault;
dataKey.inLegend = !dataKey.settings.removeFromLegend;
if (this.comparisonEnabled && dataKey.isAdditional && dataKey.settings.comparisonSettings.comparisonValuesLabel) {
dataKey.label = createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel);
} else {
@ -1242,7 +1299,7 @@ export class WidgetSubscription implements IWidgetSubscription {
const newDatasource = deepClone(configDatasource);
const entityInfo = entityDataToEntityInfo(entityData);
updateDatasourceFromEntityInfo(newDatasource, entityInfo);
newDatasource.generated = index > 0 ? true : false;
newDatasource.generated = index > 0;
return newDatasource;
}
@ -1285,16 +1342,16 @@ export class WidgetSubscription implements IWidgetSubscription {
}
}
private alarmsUpdated(alarms: Array<AlarmInfo>) {
this.notifyDataLoaded();
const updated = !this.alarms || !isEqual(this.alarms, alarms);
private alarmsLoaded(alarms: PageData<AlarmData>) {
this.alarms = alarms;
if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) {
this.updateTimewindow();
}
if (updated) {
this.onDataUpdated();
}
this.onDataUpdated();
}
private alarmsUpdated(_updated: Array<AlarmData>, alarms: PageData<AlarmData>) {
this.alarmsLoaded(alarms);
}
private updateLegend(dataIndex: number, data: DataSet, detectChanges: boolean) {

15
ui-ngx/src/app/core/ws/telemetry-websocket.service.ts

@ -16,8 +16,10 @@
import { Inject, Injectable, NgZone } from '@angular/core';
import {
AlarmDataCmd, AlarmDataUnsubscribeCmd,
AlarmDataUpdate,
AttributesSubscriptionCmd, EntityDataCmd, EntityDataUnsubscribeCmd, EntityDataUpdate,
GetHistoryCmd, isEntityDataUpdateMsg,
GetHistoryCmd, isAlarmDataUpdateMsg, isEntityDataUpdateMsg,
SubscriptionCmd,
SubscriptionUpdate,
SubscriptionUpdateMsg,
@ -107,6 +109,8 @@ export class TelemetryWebsocketService implements TelemetryService {
this.cmdsWrapper.historyCmds.push(subscriptionCommand);
} else if (subscriptionCommand instanceof EntityDataCmd) {
this.cmdsWrapper.entityDataCmds.push(subscriptionCommand);
} else if (subscriptionCommand instanceof AlarmDataCmd) {
this.cmdsWrapper.alarmDataCmds.push(subscriptionCommand);
}
}
);
@ -142,6 +146,10 @@ export class TelemetryWebsocketService implements TelemetryService {
const entityDataUnsubscribeCmd = new EntityDataUnsubscribeCmd();
entityDataUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
this.cmdsWrapper.entityDataUnsubscribeCmds.push(entityDataUnsubscribeCmd);
} else if (subscriptionCommand instanceof AlarmDataCmd) {
const alarmDataUnsubscribeCmd = new AlarmDataUnsubscribeCmd();
alarmDataUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
this.cmdsWrapper.alarmDataUnsubscribeCmds.push(alarmDataUnsubscribeCmd);
}
const cmdId = subscriptionCommand.cmdId;
if (cmdId) {
@ -281,6 +289,11 @@ export class TelemetryWebsocketService implements TelemetryService {
if (subscriber) {
subscriber.onEntityData(new EntityDataUpdate(message));
}
} else if (isAlarmDataUpdateMsg(message)) {
subscriber = this.subscribersMap.get(message.cmdId);
if (subscriber) {
subscriber.onAlarmData(new AlarmDataUpdate(message));
}
} else if (message.subscriptionId) {
subscriber = this.subscribersMap.get(message.subscriptionId);
if (subscriber) {

7
ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.html

@ -62,7 +62,7 @@
</mat-toolbar>
<div fxFlex class="table-container">
<table mat-table [dataSource]="alarmsDatasource"
matSort [matSortActive]="sortOrderProperty" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear>
matSort [matSortActive]="sortOrderProperty" [matSortDirection]="pageLinkSortDirection()" matSortDisableClear>
<ng-container matColumnDef="select" sticky>
<mat-header-cell *matHeaderCellDef style="width: 30px;">
<mat-checkbox (change)="$event ? alarmsDatasource.masterToggle() : null"
@ -125,9 +125,12 @@
*matRowDef="let alarm; columns: displayedColumns;"
(click)="onRowClick($event, alarm)"></mat-row>
</table>
<span [fxShow]="alarmsDatasource.isEmpty() | async"
<span [fxShow]="(alarmsDatasource.isEmpty() | async) && !alarmsDatasource.dataLoading"
fxLayoutAlign="center center"
class="no-data-found" translate>alarm.no-alarms-prompt</span>
<span [fxShow]="alarmsDatasource.dataLoading"
fxLayoutAlign="center center"
class="no-data-found">{{ 'common.loading' | translate }}</span>
</div>
<mat-divider *ngIf="displayPagination"></mat-divider>
<mat-paginator *ngIf="displayPagination"

243
ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts

@ -29,21 +29,21 @@ import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { WidgetAction, WidgetContext } from '@home/models/widget-component.models';
import { Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models/widget.models';
import { DataKey, Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models/widget.models';
import { IWidgetSubscription } from '@core/api/widget-api.models';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { deepClone, isDefined, isNumber, createLabelFromDatasource, hashCode } from '@core/utils';
import { createLabelFromDatasource, deepClone, hashCode, isDefined, isNumber } from '@core/utils';
import cssjs from '@core/css/css';
import { PageLink } from '@shared/models/page/page-link';
import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
import { sortItems } from '@shared/models/page/page-link';
import { Direction } from '@shared/models/page/sort-order';
import { CollectionViewer, DataSource, SelectionModel } from '@angular/cdk/collections';
import { BehaviorSubject, forkJoin, fromEvent, merge, Observable, of } from 'rxjs';
import { BehaviorSubject, forkJoin, fromEvent, merge, Observable } from 'rxjs';
import { emptyPageData, PageData } from '@shared/models/page/page-data';
import { entityTypeTranslations } from '@shared/models/entity-type.models';
import { catchError, debounceTime, distinctUntilChanged, map, take, tap } from 'rxjs/operators';
import { debounceTime, distinctUntilChanged, map, take, tap } from 'rxjs/operators';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { MatSort, SortDirection } from '@angular/material/sort';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import {
CellContentInfo,
@ -51,14 +51,16 @@ import {
constructTableCssString,
DisplayColumn,
EntityColumn,
fromAlarmColumnDef,
entityDataSortOrderFromString,
findColumnByEntityKey,
findEntityKeyByColumnDef,
fromEntityColumnDef,
getAlarmValue,
getCellContentInfo,
getCellStyleInfo,
getColumnWidth,
TableWidgetDataKeySettings,
TableWidgetSettings,
toAlarmColumnDef,
widthStyle
} from '@home/components/widget/lib/table-widget.models';
import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
@ -69,8 +71,8 @@ import {
DisplayColumnsPanelData
} from '@home/components/widget/lib/display-columns-panel.component';
import {
AlarmDataInfo,
alarmFields,
AlarmInfo,
alarmSeverityColors,
alarmSeverityTranslations,
AlarmStatus,
@ -90,6 +92,15 @@ import { MatDialog } from '@angular/material/dialog';
import { NULL_UUID } from '@shared/models/id/has-uuid';
import { DialogService } from '@core/services/dialog.service';
import { AlarmService } from '@core/http/alarm.service';
import {
AlarmData,
AlarmDataPageLink,
dataKeyToEntityKey,
dataKeyTypeToEntityKeyType,
entityDataPageLinkSortDirection,
KeyFilter
} from '@app/shared/models/query/query.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
interface AlarmsTableWidgetSettings extends TableWidgetSettings {
alarmsTitle: string;
@ -125,7 +136,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
public displayPagination = true;
public enableStickyAction = false;
public pageSizeOptions;
public pageLink: PageLink;
public pageLink: AlarmDataPageLink;
public sortOrderProperty: string;
public textSearchMode = false;
public columns: Array<EntityColumn> = [];
@ -189,9 +200,11 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
private dialogService: DialogService,
private alarmService: AlarmService) {
super(store);
const sortOrder: SortOrder = sortOrderFromString(this.defaultSortOrder);
this.pageLink = new PageLink(this.defaultPageSize, 0, null, sortOrder);
this.pageLink = {
page: 0,
pageSize: this.defaultPageSize,
textSearch: null
};
}
ngOnInit(): void {
@ -232,11 +245,15 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
public onDataUpdated() {
this.ngZone.run(() => {
this.alarmsDatasource.updateAlarms(this.subscription.alarms);
this.alarmsDatasource.updateAlarms();
this.ctx.detectChanges();
});
}
public pageLinkSortDirection(): SortDirection {
return entityDataPageLinkSortDirection(this.pageLink);
}
private initializeConfig() {
this.ctx.widgetActions = [this.searchAction, this.statusFilterAction, this.columnDisplayAction];
@ -304,6 +321,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];
this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY;
// TODO: search status, severity, types, searchPropagatedAlarms from widget config to pageLink
this.pageLink.searchPropagatedAlarms = false; // true for old widget configs
this.pageLink.severityList = [];
this.pageLink.statusList = [];
this.pageLink.typeList = [];
const cssString = constructTableCssString(this.widgetConfig);
const cssParser = new cssjs();
cssParser.testMode = false;
@ -319,9 +342,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
this.displayedColumns.push('select');
}
const latestDataKeys: Array<DataKey> = [];
if (this.alarmSource) {
this.alarmSource.dataKeys.forEach((alarmDataKey) => {
const dataKey: EntityColumn = deepClone(alarmDataKey) as EntityColumn;
dataKey.entityKey = dataKeyToEntityKey(alarmDataKey);
dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label);
dataKey.def = 'def' + this.columns.length;
const keySettings: TableWidgetDataKeySettings = dataKey.settings;
@ -330,19 +356,28 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx');
this.columnWidth[dataKey.def] = getColumnWidth(keySettings);
this.columns.push(dataKey);
if (dataKey.type !== DataKeyType.alarm) {
latestDataKeys.push(dataKey);
}
});
this.displayedColumns.push(...this.columns.map(column => column.def));
}
if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) {
this.defaultSortOrder = this.settings.defaultSortOrder;
}
this.pageLink.sortOrder = sortOrderFromString(this.defaultSortOrder);
this.sortOrderProperty = toAlarmColumnDef(this.pageLink.sortOrder.property, this.columns);
this.pageLink.sortOrder = entityDataSortOrderFromString(this.defaultSortOrder, this.columns);
let sortColumn: EntityColumn;
if (this.pageLink.sortOrder) {
sortColumn = findColumnByEntityKey(this.pageLink.sortOrder.key, this.columns);
}
this.sortOrderProperty = sortColumn ? sortColumn.def : null;
if (this.actionCellDescriptors.length) {
this.displayedColumns.push('actions');
}
this.alarmsDatasource = new AlarmsDatasource();
this.alarmsDatasource = new AlarmsDatasource(this.subscription, latestDataKeys);
if (this.enableSelection) {
this.alarmsDatasource.selectionModeChanged$.subscribe((selectionMode) => {
const hideTitlePanel = selectionMode || this.textSearchMode;
@ -467,9 +502,13 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
} else {
this.pageLink.page = 0;
}
this.pageLink.sortOrder.property = fromAlarmColumnDef(this.sort.active, this.columns);
this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()];
this.alarmsDatasource.loadAlarms(this.pageLink);
this.pageLink.sortOrder = {
key: findEntityKeyByColumnDef(this.sort.active, this.columns),
direction: Direction[this.sort.direction.toUpperCase()]
};
const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns);
const keyFilters: KeyFilter[] = null; // TODO:
this.alarmsDatasource.loadAlarms(this.pageLink, sortOrderLabel, keyFilters);
this.ctx.detectChanges();
}
@ -482,7 +521,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
return widthStyle(columnWidth);
}
public cellStyle(alarm: AlarmInfo, key: EntityColumn): any {
public cellStyle(alarm: AlarmDataInfo, key: EntityColumn): any {
let style: any = {};
if (alarm && key) {
const styleInfo = this.stylesInfo[key.def];
@ -504,7 +543,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
return style;
}
public cellContent(alarm: AlarmInfo, key: EntityColumn): SafeHtml {
public cellContent(alarm: AlarmDataInfo, key: EntityColumn): SafeHtml {
if (alarm && key) {
const contentInfo = this.contentsInfo[key.def];
const value = getAlarmValue(alarm, key);
@ -524,7 +563,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
}
}
public onRowClick($event: Event, alarm: AlarmInfo) {
public onRowClick($event: Event, alarm: AlarmDataInfo) {
if ($event) {
$event.stopPropagation();
}
@ -541,7 +580,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
}
}
public onActionButtonClick($event: Event, alarm: AlarmInfo, actionDescriptor: AlarmWidgetActionDescriptor) {
public onActionButtonClick($event: Event, alarm: AlarmDataInfo, actionDescriptor: AlarmWidgetActionDescriptor) {
if (actionDescriptor.details) {
this.openAlarmDetails($event, alarm);
} else if (actionDescriptor.acknowledge) {
@ -562,7 +601,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
}
}
public actionEnabled(alarm: AlarmInfo, actionDescriptor: AlarmWidgetActionDescriptor): boolean {
public actionEnabled(alarm: AlarmDataInfo, actionDescriptor: AlarmWidgetActionDescriptor): boolean {
if (actionDescriptor.acknowledge) {
return (alarm.status === AlarmStatus.ACTIVE_UNACK ||
alarm.status === AlarmStatus.CLEARED_UNACK);
@ -573,7 +612,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
return true;
}
private openAlarmDetails($event: Event, alarm: AlarmInfo) {
private openAlarmDetails($event: Event, alarm: AlarmDataInfo) {
if ($event) {
$event.stopPropagation();
}
@ -599,7 +638,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
}
}
private ackAlarm($event: Event, alarm: AlarmInfo) {
private ackAlarm($event: Event, alarm: AlarmDataInfo) {
if ($event) {
$event.stopPropagation();
}
@ -626,12 +665,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
$event.stopPropagation();
}
if (this.alarmsDatasource.selection.hasValue()) {
const alarms = this.alarmsDatasource.selection.selected.filter(
(alarm) => alarm.id.id !== NULL_UUID
const alarmIds = this.alarmsDatasource.selection.selected.filter(
(alarmId) => alarmId !== NULL_UUID
);
if (alarms.length) {
const title = this.translate.instant('alarm.aknowledge-alarms-title', {count: alarms.length});
const content = this.translate.instant('alarm.aknowledge-alarms-text', {count: alarms.length});
if (alarmIds.length) {
const title = this.translate.instant('alarm.aknowledge-alarms-title', {count: alarmIds.length});
const content = this.translate.instant('alarm.aknowledge-alarms-text', {count: alarmIds.length});
this.dialogService.confirm(
title,
content,
@ -641,8 +680,8 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
if (res) {
if (res) {
const tasks: Observable<void>[] = [];
for (const alarm of alarms) {
tasks.push(this.alarmService.ackAlarm(alarm.id.id));
for (const alarmId of alarmIds) {
tasks.push(this.alarmService.ackAlarm(alarmId));
}
forkJoin(tasks).subscribe(() => {
this.alarmsDatasource.clearSelection();
@ -655,7 +694,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
}
}
private clearAlarm($event: Event, alarm: AlarmInfo) {
private clearAlarm($event: Event, alarm: AlarmDataInfo) {
if ($event) {
$event.stopPropagation();
}
@ -682,12 +721,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
$event.stopPropagation();
}
if (this.alarmsDatasource.selection.hasValue()) {
const alarms = this.alarmsDatasource.selection.selected.filter(
(alarm) => alarm.id.id !== NULL_UUID
const alarmIds = this.alarmsDatasource.selection.selected.filter(
(alarmId) => alarmId !== NULL_UUID
);
if (alarms.length) {
const title = this.translate.instant('alarm.clear-alarms-title', {count: alarms.length});
const content = this.translate.instant('alarm.clear-alarms-text', {count: alarms.length});
if (alarmIds.length) {
const title = this.translate.instant('alarm.clear-alarms-title', {count: alarmIds.length});
const content = this.translate.instant('alarm.clear-alarms-text', {count: alarmIds.length});
this.dialogService.confirm(
title,
content,
@ -697,8 +736,8 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
if (res) {
if (res) {
const tasks: Observable<void>[] = [];
for (const alarm of alarms) {
tasks.push(this.alarmService.clearAlarm(alarm.id.id));
for (const alarmId of alarmIds) {
tasks.push(this.alarmService.clearAlarm(alarmId));
}
forkJoin(tasks).subscribe(() => {
this.alarmsDatasource.clearSelection();
@ -756,27 +795,29 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
}
class AlarmsDatasource implements DataSource<AlarmInfo> {
class AlarmsDatasource implements DataSource<AlarmDataInfo> {
private alarmsSubject = new BehaviorSubject<AlarmInfo[]>([]);
private pageDataSubject = new BehaviorSubject<PageData<AlarmInfo>>(emptyPageData<AlarmInfo>());
private alarmsSubject = new BehaviorSubject<AlarmDataInfo[]>([]);
private pageDataSubject = new BehaviorSubject<PageData<AlarmDataInfo>>(emptyPageData<AlarmDataInfo>());
public selection = new SelectionModel<AlarmInfo>(true, [], false);
public selection = new SelectionModel<string>(true, [], false);
private selectionModeChanged = new EventEmitter<boolean>();
public selectionModeChanged$ = this.selectionModeChanged.asObservable();
public selectionModeChanged$ = this.selectionModeChanged.asObservable();
private currentAlarm: AlarmDataInfo = null;
private allAlarms: Array<AlarmInfo> = [];
private allAlarmsSubject = new BehaviorSubject<AlarmInfo[]>([]);
private allAlarms$: Observable<Array<AlarmInfo>> = this.allAlarmsSubject.asObservable();
public dataLoading = true;
private currentAlarm: AlarmInfo = null;
private appliedPageLink: AlarmDataPageLink;
private appliedSortOrderLabel: string;
constructor() {
constructor(private subscription: IWidgetSubscription,
private dataKeys: Array<DataKey>) {
}
connect(collectionViewer: CollectionViewer): Observable<AlarmInfo[] | ReadonlyArray<AlarmInfo>> {
connect(collectionViewer: CollectionViewer): Observable<AlarmDataInfo[] | ReadonlyArray<AlarmDataInfo>> {
return this.alarmsSubject.asObservable();
}
@ -785,51 +826,63 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
this.pageDataSubject.complete();
}
loadAlarms(pageLink: PageLink) {
loadAlarms(pageLink: AlarmDataPageLink, sortOrderLabel: string, keyFilters: KeyFilter[]) {
if (this.selection.hasValue()) {
this.selection.clear();
this.onSelectionModeChanged(false);
}
this.fetchAlarms(pageLink).pipe(
catchError(() => of(emptyPageData<AlarmInfo>())),
).subscribe(
(pageData) => {
this.alarmsSubject.next(pageData.data);
this.pageDataSubject.next(pageData);
}
);
this.appliedPageLink = pageLink;
this.appliedSortOrderLabel = sortOrderLabel;
this.subscription.subscribeForAlarms(pageLink, keyFilters);
}
updateAlarms(alarms: AlarmInfo[]) {
alarms.forEach((newAlarm) => {
const existingAlarmIndex = this.allAlarms.findIndex(alarm => alarm.id.id === newAlarm.id.id);
if (existingAlarmIndex > -1) {
Object.assign(this.allAlarms[existingAlarmIndex], newAlarm);
} else {
this.allAlarms.push(newAlarm);
}
updateAlarms() {
const subscriptionAlarms = this.subscription.alarms;
let alarms = new Array<AlarmDataInfo>();
subscriptionAlarms.data.forEach((alarmData) => {
alarms.push(this.alarmDataToInfo(alarmData));
});
for (let i = this.allAlarms.length - 1; i >= 0; i--) {
const oldAlarm = this.allAlarms[i];
const newAlarmIndex = alarms.findIndex(alarm => alarm.id.id === oldAlarm.id.id);
if (newAlarmIndex === -1) {
this.allAlarms.splice(i, 1);
}
if (this.appliedSortOrderLabel && this.appliedSortOrderLabel.length) {
const asc = this.appliedPageLink.sortOrder.direction === Direction.ASC;
alarms = alarms.sort((a, b) => sortItems(a, b, this.appliedSortOrderLabel, asc));
}
if (this.selection.hasValue()) {
const toRemove: AlarmInfo[] = [];
this.selection.selected.forEach((selectedAlarm) => {
const existingAlarm = this.allAlarms.find(alarm => alarm.id.id === selectedAlarm.id.id);
if (!existingAlarm) {
toRemove.push(selectedAlarm);
}
});
const alarmIds = alarms.map((alarm) => alarm.id.id);
const toRemove = this.selection.selected.filter(alarmId => alarmIds.indexOf(alarmId) === -1);
this.selection.deselect(...toRemove);
if (this.selection.isEmpty()) {
this.onSelectionModeChanged(false);
}
}
this.allAlarmsSubject.next(this.allAlarms);
const alarmsPageData: PageData<AlarmDataInfo> = {
data: alarms,
totalPages: subscriptionAlarms.totalPages,
totalElements: subscriptionAlarms.totalElements,
hasNext: subscriptionAlarms.hasNext
};
this.alarmsSubject.next(alarms);
this.pageDataSubject.next(alarmsPageData);
this.dataLoading = false;
}
private alarmDataToInfo(alarmData: AlarmData): AlarmDataInfo {
const alarm: AlarmDataInfo = deepClone(alarmData);
delete alarm.latest;
const latest = alarmData.latest;
this.dataKeys.forEach((dataKey, index) => {
const type = dataKeyTypeToEntityKeyType(dataKey.type);
let value = '';
if (type) {
if (latest && latest[type]) {
const tsVal = latest[type][dataKey.name];
if (tsVal) {
value = tsVal.value;
}
}
}
alarm[dataKey.label] = value;
});
return alarm;
}
isAllSelected(): Observable<boolean> {
@ -851,16 +904,16 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
);
}
toggleSelection(alarm: AlarmInfo) {
toggleSelection(alarm: AlarmDataInfo) {
const hasValue = this.selection.hasValue();
this.selection.toggle(alarm);
this.selection.toggle(alarm.id.id);
if (hasValue !== this.selection.hasValue()) {
this.onSelectionModeChanged(this.selection.hasValue());
}
}
isSelected(alarm: AlarmInfo): boolean {
return this.selection.isSelected(alarm);
isSelected(alarm: AlarmDataInfo): boolean {
return this.selection.isSelected(alarm.id.id);
}
clearSelection() {
@ -881,7 +934,7 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
}
} else {
alarms.forEach(row => {
this.selection.select(row);
this.selection.select(row.id.id);
});
if (numSelected === 0) {
this.onSelectionModeChanged(true);
@ -892,7 +945,7 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
).subscribe();
}
public toggleCurrentAlarm(alarm: AlarmInfo): boolean {
public toggleCurrentAlarm(alarm: AlarmDataInfo): boolean {
if (this.currentAlarm !== alarm) {
this.currentAlarm = alarm;
return true;
@ -901,7 +954,7 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
}
}
public isCurrentAlarm(alarm: AlarmInfo): boolean {
public isCurrentAlarm(alarm: AlarmDataInfo): boolean {
return (this.currentAlarm && alarm && this.currentAlarm.id && alarm.id) &&
(this.currentAlarm.id.id === alarm.id.id);
}
@ -909,10 +962,4 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
private onSelectionModeChanged(selectionMode: boolean) {
this.selectionModeChanged.emit(selectionMode);
}
private fetchAlarms(pageLink: PageLink): Observable<PageData<AlarmInfo>> {
return this.allAlarms$.pipe(
map((data) => pageLink.filterData(data))
);
}
}

15
ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts

@ -77,6 +77,7 @@ import {
DisplayColumnsPanelData
} from '@home/components/widget/lib/display-columns-panel.component';
import {
dataKeyToEntityKey,
Direction,
EntityDataPageLink,
entityDataPageLinkSortDirection,
@ -205,7 +206,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
public onDataUpdated() {
this.ngZone.run(() => {
this.entityDatasource.dataUpdated(); // .updateEntitiesData(this.subscription.data);
this.entityDatasource.dataUpdated();
this.ctx.detectChanges();
});
}
@ -339,19 +340,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
if (datasource && datasource.dataKeys) {
datasource.dataKeys.forEach((entityDataKey) => {
const dataKey: EntityColumn = deepClone(entityDataKey) as EntityColumn;
dataKey.entityKey = {
key: dataKey.name,
type: null
};
dataKey.entityKey = dataKeyToEntityKey(entityDataKey);
if (dataKey.type === DataKeyType.function) {
dataKey.name = dataKey.label;
dataKey.entityKey.type = EntityKeyType.ENTITY_FIELD;
} else if (dataKey.type === DataKeyType.entityField) {
dataKey.entityKey.type = EntityKeyType.ENTITY_FIELD;
} else if (dataKey.type === DataKeyType.attribute) {
dataKey.entityKey.type = EntityKeyType.ATTRIBUTE;
} else if (dataKey.type === DataKeyType.timeseries) {
dataKey.entityKey.type = EntityKeyType.TIME_SERIES;
}
dataKeys.push(dataKey);

17
ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts

@ -17,9 +17,10 @@
import { EntityId } from '@shared/models/id/entity-id';
import { DataKey, WidgetConfig } from '@shared/models/widget.models';
import { getDescendantProp, isDefined } from '@core/utils';
import { alarmFields, AlarmInfo } from '@shared/models/alarm.models';
import { AlarmDataInfo, alarmFields } from '@shared/models/alarm.models';
import * as tinycolor_ from 'tinycolor2';
import { Direction, EntityDataSortOrder, EntityKey } from '@shared/models/query/query.models';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
const tinycolor = tinycolor_;
@ -120,7 +121,15 @@ export function findColumn(searchProperty: string, searchValue: string, columns:
}
export function findColumnByLabel(label: string, columns: EntityColumn[]): EntityColumn {
return findColumn('label', label, columns);
let column: EntityColumn;
const alarmColumns = columns.filter(c => c.type === DataKeyType.alarm);
if (alarmColumns.length) {
column = findColumn('name', label, alarmColumns);
}
if (!column) {
column = findColumn('label', label, columns);
}
return column;
}
export function findColumnByDef(def: string, columns: EntityColumn[]): EntityColumn {
@ -160,12 +169,12 @@ export function getEntityValue(entity: any, key: DataKey): any {
return getDescendantProp(entity, key.label);
}
export function getAlarmValue(alarm: AlarmInfo, key: EntityColumn) {
export function getAlarmValue(alarm: AlarmDataInfo, key: EntityColumn) {
const alarmField = alarmFields[key.name];
if (alarmField) {
return getDescendantProp(alarm, alarmField.value);
} else {
return getDescendantProp(alarm, key.name);
return getDescendantProp(alarm, key.label);
}
}

9
ui-ngx/src/app/modules/home/components/widget/widget.component.ts

@ -94,6 +94,7 @@ import { ResizeObserver } from '@juggle/resize-observer';
import { EntityDataService } from '@core/api/entity-data.service';
import { TranslateService } from '@ngx-translate/core';
import { NotificationType } from '@core/notification/notification.models';
import { AlarmDataService } from '@core/api/alarm-data.service';
@Component({
selector: 'tb-widget',
@ -167,9 +168,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
private timeService: TimeService,
private deviceService: DeviceService,
private entityService: EntityService,
private alarmService: AlarmService,
private dashboardService: DashboardService,
private entityDataService: EntityDataService,
private alarmDataService: AlarmDataService,
private translate: TranslateService,
private utils: UtilsService,
private raf: RafService,
@ -300,9 +301,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
this.subscriptionContext = new WidgetSubscriptionContext(this.widgetContext.dashboard);
this.subscriptionContext.timeService = this.timeService;
this.subscriptionContext.deviceService = this.deviceService;
this.subscriptionContext.alarmService = this.alarmService;
this.subscriptionContext.translate = this.translate;
this.subscriptionContext.entityDataService = this.entityDataService;
this.subscriptionContext.alarmDataService = this.alarmDataService;
this.subscriptionContext.utils = this.utils;
this.subscriptionContext.raf = this.raf;
this.subscriptionContext.widgetUtils = this.widgetContext.utils;
@ -901,14 +902,14 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
};
if (this.widget.type === widgetType.alarm) {
options.alarmSource = deepClone(this.widget.config.alarmSource);
options.alarmSearchStatus = isDefined(this.widget.config.alarmSearchStatus) ?
/*options.alarmSearchStatus = isDefined(this.widget.config.alarmSearchStatus) ?
this.widget.config.alarmSearchStatus : AlarmSearchStatus.ANY;
options.alarmsPollingInterval = isDefined(this.widget.config.alarmsPollingInterval) ?
this.widget.config.alarmsPollingInterval * 1000 : 5000;
options.alarmsMaxCountLoad = isDefined(this.widget.config.alarmsMaxCountLoad) ?
this.widget.config.alarmsMaxCountLoad : 0;
options.alarmsFetchSize = isDefined(this.widget.config.alarmsFetchSize) ?
this.widget.config.alarmsFetchSize : 100;
this.widget.config.alarmsFetchSize : 100;*/
} else {
options.datasources = deepClone(this.widget.config.datasources);
}

4
ui-ngx/src/app/shared/models/alarm.models.ts

@ -103,6 +103,10 @@ export interface AlarmInfo extends Alarm {
originatorName: string;
}
export interface AlarmDataInfo extends AlarmInfo {
[key: string]: any;
}
export const simulatedAlarm: AlarmInfo = {
id: new AlarmId(NULL_UUID),
tenantId: new TenantId(NULL_UUID),

56
ui-ngx/src/app/shared/models/query/query.models.ts

@ -20,10 +20,11 @@ import { SortDirection } from '@angular/material/sort';
import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
import { EntityInfo } from '@shared/models/entity.models';
import { EntityType } from '@shared/models/entity-type.models';
import { Datasource, DatasourceType } from '@shared/models/widget.models';
import { DataKey, Datasource, DatasourceType } from '@shared/models/widget.models';
import { PageData } from '@shared/models/page/page-data';
import { isDefined, isEqual } from '@core/utils';
import { TranslateService } from '@ngx-translate/core';
import { AlarmInfo, AlarmSearchStatus, AlarmSeverity } from '../alarm.models';
export enum EntityKeyType {
ATTRIBUTE = 'ATTRIBUTE',
@ -31,7 +32,8 @@ export enum EntityKeyType {
SHARED_ATTRIBUTE = 'SHARED_ATTRIBUTE',
SERVER_ATTRIBUTE = 'SERVER_ATTRIBUTE',
TIME_SERIES = 'TIME_SERIES',
ENTITY_FIELD = 'ENTITY_FIELD'
ENTITY_FIELD = 'ENTITY_FIELD',
ALARM_FIELD = 'ENTITY_FIELD'
}
export const entityKeyTypeTranslationMap = new Map<EntityKeyType, string>(
@ -53,6 +55,23 @@ export function entityKeyTypeToDataKeyType(entityKeyType: EntityKeyType): DataKe
return DataKeyType.timeseries;
case EntityKeyType.ENTITY_FIELD:
return DataKeyType.entityField;
case EntityKeyType.ALARM_FIELD:
return DataKeyType.alarm;
}
}
export function dataKeyTypeToEntityKeyType(dataKeyType: DataKeyType): EntityKeyType {
switch (dataKeyType) {
case DataKeyType.timeseries:
return EntityKeyType.TIME_SERIES;
case DataKeyType.attribute:
return EntityKeyType.ATTRIBUTE;
case DataKeyType.function:
return EntityKeyType.ENTITY_FIELD;
case DataKeyType.alarm:
return EntityKeyType.ALARM_FIELD;
case DataKeyType.entityField:
return EntityKeyType.ENTITY_FIELD;
}
}
@ -61,6 +80,14 @@ export interface EntityKey {
key: string;
}
export function dataKeyToEntityKey(dataKey: DataKey): EntityKey {
const entityKey: EntityKey = {
key: dataKey.name,
type: dataKeyTypeToEntityKeyType(dataKey.type)
};
return entityKey;
}
export enum EntityKeyValueType {
STRING = 'STRING',
NUMERIC = 'NUMERIC',
@ -479,6 +506,16 @@ export interface EntityDataPageLink {
dynamic?: boolean;
}
export interface AlarmDataPageLink extends EntityDataPageLink {
startTs?: number;
endTs?: number;
timeWindow?: number;
typeList?: Array<string>;
statusList?: Array<AlarmSearchStatus>;
severityList?: Array<AlarmSeverity>;
searchPropagatedAlarms?: boolean;
}
export function entityDataPageLinkSortDirection(pageLink: EntityDataPageLink): SortDirection {
if (pageLink.sortOrder) {
return (pageLink.sortOrder.direction + '').toLowerCase() as SortDirection;
@ -508,13 +545,19 @@ export interface EntityCountQuery {
entityFilter: EntityFilter;
}
export interface EntityDataQuery extends EntityCountQuery {
pageLink: EntityDataPageLink;
export interface AbstractDataQuery<T extends EntityDataPageLink> extends EntityCountQuery {
pageLink: T;
entityFields?: Array<EntityKey>;
latestValues?: Array<EntityKey>;
keyFilters?: Array<KeyFilter>;
}
export interface EntityDataQuery extends AbstractDataQuery<EntityDataPageLink> {
}
export interface AlarmDataQuery extends AbstractDataQuery<AlarmDataPageLink> {
}
export interface TsValue {
ts: number;
value: string;
@ -526,6 +569,11 @@ export interface EntityData {
timeseries: {[key: string]: Array<TsValue>};
}
export interface AlarmData extends AlarmInfo {
entityId: string;
latest: {[entityKeyType: string]: {[key: string]: TsValue}};
}
export function entityPageDataChanged(prevPageData: PageData<EntityData>, nextPageData: PageData<EntityData>): boolean {
const prevIds = prevPageData.data.map((entityData) => entityData.entityId.id);
const nextIds = nextPageData.data.map((entityData) => entityData.entityId.id);

96
ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts

@ -21,7 +21,7 @@ import { Observable, ReplaySubject, Subject } from 'rxjs';
import { EntityId } from '@shared/models/id/entity-id';
import { map } from 'rxjs/operators';
import { NgZone } from '@angular/core';
import { EntityData, EntityDataQuery, EntityKey } from '@shared/models/query/query.models';
import { AlarmData, AlarmDataQuery, EntityData, EntityDataQuery, EntityKey } from '@shared/models/query/query.models';
import { PageData } from '@shared/models/page/page-data';
export enum DataKeyType {
@ -165,16 +165,31 @@ export class EntityDataCmd implements WebsocketCmd {
}
}
export class AlarmDataCmd implements WebsocketCmd {
cmdId: number;
query?: AlarmDataQuery;
public isEmpty(): boolean {
return !this.query;
}
}
export class EntityDataUnsubscribeCmd implements WebsocketCmd {
cmdId: number;
}
export class AlarmDataUnsubscribeCmd implements WebsocketCmd {
cmdId: number;
}
export class TelemetryPluginCmdsWrapper {
attrSubCmds: Array<AttributesSubscriptionCmd>;
tsSubCmds: Array<TimeseriesSubscriptionCmd>;
historyCmds: Array<GetHistoryCmd>;
entityDataCmds: Array<EntityDataCmd>;
entityDataUnsubscribeCmds: Array<EntityDataUnsubscribeCmd>;
alarmDataCmds: Array<AlarmDataCmd>;
alarmDataUnsubscribeCmds: Array<AlarmDataUnsubscribeCmd>;
constructor() {
this.attrSubCmds = [];
@ -182,6 +197,8 @@ export class TelemetryPluginCmdsWrapper {
this.historyCmds = [];
this.entityDataCmds = [];
this.entityDataUnsubscribeCmds = [];
this.alarmDataCmds = [];
this.alarmDataUnsubscribeCmds = [];
}
public hasCommands(): boolean {
@ -189,7 +206,9 @@ export class TelemetryPluginCmdsWrapper {
this.historyCmds.length > 0 ||
this.attrSubCmds.length > 0 ||
this.entityDataCmds.length > 0 ||
this.entityDataUnsubscribeCmds.length > 0;
this.entityDataUnsubscribeCmds.length > 0 ||
this.alarmDataCmds.length > 0 ||
this.alarmDataUnsubscribeCmds.length > 0;
}
public clear() {
@ -198,6 +217,8 @@ export class TelemetryPluginCmdsWrapper {
this.historyCmds.length = 0;
this.entityDataCmds.length = 0;
this.entityDataUnsubscribeCmds.length = 0;
this.alarmDataCmds.length = 0;
this.alarmDataUnsubscribeCmds.length = 0;
}
public preparePublishCommands(maxCommands: number): TelemetryPluginCmdsWrapper {
@ -212,6 +233,10 @@ export class TelemetryPluginCmdsWrapper {
preparedWrapper.entityDataCmds = this.popCmds(this.entityDataCmds, leftCount);
leftCount -= preparedWrapper.entityDataCmds.length;
preparedWrapper.entityDataUnsubscribeCmds = this.popCmds(this.entityDataUnsubscribeCmds, leftCount);
leftCount -= preparedWrapper.entityDataUnsubscribeCmds.length;
preparedWrapper.alarmDataCmds = this.popCmds(this.alarmDataCmds, leftCount);
leftCount -= preparedWrapper.alarmDataCmds.length;
preparedWrapper.alarmDataUnsubscribeCmds = this.popCmds(this.alarmDataUnsubscribeCmds, leftCount);
return preparedWrapper;
}
@ -239,18 +264,38 @@ export interface SubscriptionUpdateMsg extends SubscriptionDataHolder {
errorMsg: string;
}
export interface EntityDataUpdateMsg {
export enum DataUpdateType {
ENTITY_DATA = 'ENTITY_DATA',
ALARM_DATA = 'ALARM_DATA'
}
export interface DataUpdateMsg<T> {
cmdId: number;
data?: PageData<EntityData>;
update?: Array<EntityData>;
data?: PageData<T>;
update?: Array<T>;
errorCode: number;
errorMsg: string;
dataUpdateType: DataUpdateType;
}
export interface EntityDataUpdateMsg extends DataUpdateMsg<EntityData> {
dataUpdateType: DataUpdateType.ENTITY_DATA;
}
export type WebsocketDataMsg = EntityDataUpdateMsg | SubscriptionUpdateMsg;
export interface AlarmDataUpdateMsg extends DataUpdateMsg<AlarmData> {
dataUpdateType: DataUpdateType.ALARM_DATA;
}
export type WebsocketDataMsg = AlarmDataUpdateMsg | EntityDataUpdateMsg | SubscriptionUpdateMsg;
export function isEntityDataUpdateMsg(message: WebsocketDataMsg): message is EntityDataUpdateMsg {
return (message as EntityDataUpdateMsg).cmdId !== undefined;
const updateMsg = (message as DataUpdateMsg<any>);
return updateMsg.cmdId !== undefined && updateMsg.dataUpdateType === DataUpdateType.ENTITY_DATA;
}
export function isAlarmDataUpdateMsg(message: WebsocketDataMsg): message is AlarmDataUpdateMsg {
const updateMsg = (message as DataUpdateMsg<any>);
return updateMsg.cmdId !== undefined && updateMsg.dataUpdateType === DataUpdateType.ALARM_DATA;
}
export class SubscriptionUpdate implements SubscriptionUpdateMsg {
@ -302,19 +347,33 @@ export class SubscriptionUpdate implements SubscriptionUpdateMsg {
}
}
export class EntityDataUpdate implements EntityDataUpdateMsg {
export class DataUpdate<T> implements DataUpdateMsg<T> {
cmdId: number;
errorCode: number;
errorMsg: string;
data?: PageData<EntityData>;
update?: Array<EntityData>;
data?: PageData<T>;
update?: Array<T>;
dataUpdateType: DataUpdateType;
constructor(msg: EntityDataUpdateMsg) {
constructor(msg: DataUpdateMsg<T>) {
this.cmdId = msg.cmdId;
this.errorCode = msg.errorCode;
this.errorMsg = msg.errorMsg;
this.data = msg.data;
this.update = msg.update;
this.dataUpdateType = msg.dataUpdateType;
}
}
export class EntityDataUpdate extends DataUpdate<EntityData> {
constructor(msg: EntityDataUpdateMsg) {
super(msg);
}
}
export class AlarmDataUpdate extends DataUpdate<AlarmData> {
constructor(msg: AlarmDataUpdateMsg) {
super(msg);
}
}
@ -328,6 +387,7 @@ export class TelemetrySubscriber {
private dataSubject = new ReplaySubject<SubscriptionUpdate>(1);
private entityDataSubject = new ReplaySubject<EntityDataUpdate>(1);
private alarmDataSubject = new ReplaySubject<AlarmDataUpdate>(1);
private reconnectSubject = new Subject();
private zone: NgZone;
@ -336,6 +396,7 @@ export class TelemetrySubscriber {
public data$ = this.dataSubject.asObservable();
public entityData$ = this.entityDataSubject.asObservable();
public alarmData$ = this.alarmDataSubject.asObservable();
public reconnect$ = this.reconnectSubject.asObservable();
public static createEntityAttributesSubscription(telemetryService: TelemetryService,
@ -379,6 +440,7 @@ export class TelemetrySubscriber {
public complete() {
this.dataSubject.complete();
this.entityDataSubject.complete();
this.alarmDataSubject.complete();
this.reconnectSubject.complete();
}
@ -416,6 +478,18 @@ export class TelemetrySubscriber {
}
}
public onAlarmData(message: AlarmDataUpdate) {
if (this.zone) {
this.zone.run(
() => {
this.alarmDataSubject.next(message);
}
);
} else {
this.alarmDataSubject.next(message);
}
}
public onReconnected() {
this.reconnectSubject.next();
}

Loading…
Cancel
Save