Commit 4747e110bc29eabdc43fe54f1aebe844f643811e

Authored by Igor Kulikov
1 parent 7a19dee3

UI: Alarm data query support

@@ -256,7 +256,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc @@ -256,7 +256,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
256 } else { 256 } else {
257 entitiesSortOrder = sortOrder; 257 entitiesSortOrder = sortOrder;
258 } 258 }
259 - EntityDataPageLink edpl = new EntityDataPageLink(0, maxEntitiesPerAlarmSubscription, null, entitiesSortOrder); 259 + EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null, entitiesSortOrder);
260 EntityDataQuery edq = new EntityDataQuery(adq.getEntityFilter(), edpl, adq.getEntityFields(), adq.getLatestValues(), adq.getKeyFilters()); 260 EntityDataQuery edq = new EntityDataQuery(adq.getEntityFilter(), edpl, adq.getEntityFields(), adq.getLatestValues(), adq.getKeyFilters());
261 PageData<EntityData> entitiesData = entityService.findEntityDataByQuery(ctx.getTenantId(), ctx.getCustomerId(), edq); 261 PageData<EntityData> entitiesData = entityService.findEntityDataByQuery(ctx.getTenantId(), ctx.getCustomerId(), edq);
262 List<EntityData> entities = entitiesData.getData(); 262 List<EntityData> entities = entitiesData.getData();
@@ -35,6 +35,11 @@ public class AlarmDataUpdate extends DataUpdate<AlarmData> { @@ -35,6 +35,11 @@ public class AlarmDataUpdate extends DataUpdate<AlarmData> {
35 super(cmdId, null, null, errorCode, errorMsg); 35 super(cmdId, null, null, errorCode, errorMsg);
36 } 36 }
37 37
  38 + @Override
  39 + public DataUpdateType getDataUpdateType() {
  40 + return DataUpdateType.ALARM_DATA;
  41 + }
  42 +
38 @JsonCreator 43 @JsonCreator
39 public AlarmDataUpdate(@JsonProperty("cmdId") int cmdId, 44 public AlarmDataUpdate(@JsonProperty("cmdId") int cmdId,
40 @JsonProperty("data") PageData<AlarmData> data, 45 @JsonProperty("data") PageData<AlarmData> data,
@@ -40,4 +40,5 @@ public abstract class DataUpdate<T> { @@ -40,4 +40,5 @@ public abstract class DataUpdate<T> {
40 this(cmdId, null, null, errorCode, errorMsg); 40 this(cmdId, null, null, errorCode, errorMsg);
41 } 41 }
42 42
  43 + public abstract DataUpdateType getDataUpdateType();
43 } 44 }
  1 +/**
  2 + * Copyright © 2016-2020 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.telemetry.cmd.v2;
  17 +
  18 +public enum DataUpdateType {
  19 + ENTITY_DATA,
  20 + ALARM_DATA
  21 +}
@@ -33,6 +33,11 @@ public class EntityDataUpdate extends DataUpdate<EntityData> { @@ -33,6 +33,11 @@ public class EntityDataUpdate extends DataUpdate<EntityData> {
33 super(cmdId, null, null, errorCode, errorMsg); 33 super(cmdId, null, null, errorCode, errorMsg);
34 } 34 }
35 35
  36 + @Override
  37 + public DataUpdateType getDataUpdateType() {
  38 + return DataUpdateType.ENTITY_DATA;
  39 + }
  40 +
36 @JsonCreator 41 @JsonCreator
37 public EntityDataUpdate(@JsonProperty("cmdId") int cmdId, 42 public EntityDataUpdate(@JsonProperty("cmdId") int cmdId,
38 @JsonProperty("data") PageData<EntityData> data, 43 @JsonProperty("data") PageData<EntityData> data,
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import {
  18 + AlarmDataCmd,
  19 + DataKeyType,
  20 + TelemetryService,
  21 + TelemetrySubscriber
  22 +} from '@shared/models/telemetry/telemetry.models';
  23 +import { DatasourceType } from '@shared/models/widget.models';
  24 +import {
  25 + AlarmData,
  26 + AlarmDataPageLink,
  27 + EntityFilter,
  28 + EntityKey,
  29 + EntityKeyType,
  30 + KeyFilter
  31 +} from '@shared/models/query/query.models';
  32 +import { SubscriptionTimewindow } from '@shared/models/time/time.models';
  33 +import { AlarmDataListener } from '@core/api/alarm-data.service';
  34 +import { UtilsService } from '@core/services/utils.service';
  35 +import { PageData } from '@shared/models/page/page-data';
  36 +import { deepClone, isDefined, isDefinedAndNotNull, isObject } from '@core/utils';
  37 +import { simulatedAlarm } from '@shared/models/alarm.models';
  38 +
  39 +export interface AlarmSubscriptionDataKey {
  40 + name: string;
  41 + type: DataKeyType;
  42 +}
  43 +
  44 +export interface AlarmDataSubscriptionOptions {
  45 + datasourceType: DatasourceType;
  46 + dataKeys: Array<AlarmSubscriptionDataKey>;
  47 + entityFilter?: EntityFilter;
  48 + pageLink?: AlarmDataPageLink;
  49 + keyFilters?: Array<KeyFilter>;
  50 + additionalKeyFilters?: Array<KeyFilter>;
  51 + subscriptionTimewindow?: SubscriptionTimewindow;
  52 +}
  53 +
  54 +export class AlarmDataSubscription {
  55 +
  56 + private datasourceType: DatasourceType = this.alarmDataSubscriptionOptions.datasourceType;
  57 +
  58 + private history: boolean;
  59 + private realtime: boolean;
  60 +
  61 + private subscriber: TelemetrySubscriber;
  62 + private alarmDataCommand: AlarmDataCmd;
  63 +
  64 + private pageData: PageData<AlarmData>;
  65 + private alarmIdToDataIndex: {[id: string]: number};
  66 +
  67 + private subsTw: SubscriptionTimewindow;
  68 +
  69 + constructor(public alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions,
  70 + private listener: AlarmDataListener,
  71 + private telemetryService: TelemetryService,
  72 + private utils: UtilsService) {
  73 + }
  74 +
  75 + public unsubscribe() {
  76 + if (this.datasourceType === DatasourceType.entity) {
  77 + if (this.subscriber) {
  78 + this.subscriber.unsubscribe();
  79 + this.subscriber = null;
  80 + }
  81 + }
  82 + }
  83 +
  84 + public subscribe() {
  85 + this.subsTw = this.alarmDataSubscriptionOptions.subscriptionTimewindow;
  86 + this.history = this.alarmDataSubscriptionOptions.subscriptionTimewindow &&
  87 + isObject(this.alarmDataSubscriptionOptions.subscriptionTimewindow.fixedWindow);
  88 + this.realtime = this.alarmDataSubscriptionOptions.subscriptionTimewindow &&
  89 + isDefinedAndNotNull(this.alarmDataSubscriptionOptions.subscriptionTimewindow.realtimeWindowMs);
  90 + if (this.datasourceType === DatasourceType.entity) {
  91 + this.subscriber = new TelemetrySubscriber(this.telemetryService);
  92 + this.alarmDataCommand = new AlarmDataCmd();
  93 +
  94 + const entityFields: Array<EntityKey> =
  95 + this.alarmDataSubscriptionOptions.dataKeys.filter(dataKey => dataKey.type === DataKeyType.entityField).map(
  96 + dataKey => ({ type: EntityKeyType.ENTITY_FIELD, key: dataKey.name })
  97 + );
  98 +
  99 + const attrFields = this.alarmDataSubscriptionOptions.dataKeys.filter(dataKey => dataKey.type === DataKeyType.attribute).map(
  100 + dataKey => ({ type: EntityKeyType.ATTRIBUTE, key: dataKey.name })
  101 + );
  102 + const tsFields = this.alarmDataSubscriptionOptions.dataKeys.filter(dataKey => dataKey.type === DataKeyType.timeseries).map(
  103 + dataKey => ({ type: EntityKeyType.TIME_SERIES, key: dataKey.name })
  104 + );
  105 + const latestValues = attrFields.concat(tsFields);
  106 +
  107 + let keyFilters = this.alarmDataSubscriptionOptions.keyFilters;
  108 + if (this.alarmDataSubscriptionOptions.additionalKeyFilters) {
  109 + if (keyFilters) {
  110 + keyFilters = keyFilters.concat(this.alarmDataSubscriptionOptions.additionalKeyFilters);
  111 + } else {
  112 + keyFilters = this.alarmDataSubscriptionOptions.additionalKeyFilters;
  113 + }
  114 + }
  115 + this.alarmDataCommand.query = {
  116 + entityFilter: this.alarmDataSubscriptionOptions.entityFilter,
  117 + pageLink: deepClone(this.alarmDataSubscriptionOptions.pageLink),
  118 + keyFilters,
  119 + entityFields,
  120 + latestValues
  121 + };
  122 + if (this.history) {
  123 + this.alarmDataCommand.query.pageLink.startTs = this.subsTw.fixedWindow.startTimeMs;
  124 + this.alarmDataCommand.query.pageLink.endTs = this.subsTw.fixedWindow.endTimeMs;
  125 + } else {
  126 + this.alarmDataCommand.query.pageLink.timeWindow = this.subsTw.realtimeWindowMs;
  127 + }
  128 +
  129 + this.subscriber.subscriptionCommands.push(this.alarmDataCommand);
  130 +
  131 + this.subscriber.alarmData$.subscribe((alarmDataUpdate) => {
  132 + if (alarmDataUpdate.data) {
  133 + this.onPageData(alarmDataUpdate.data);
  134 + } else if (alarmDataUpdate.update) {
  135 + this.onDataUpdate(alarmDataUpdate.update);
  136 + }
  137 + });
  138 +
  139 + this.subscriber.subscribe();
  140 +
  141 + } else if (this.datasourceType === DatasourceType.function) {
  142 + const pageData: PageData<AlarmData> = {
  143 + data: [{...simulatedAlarm, entityId: '1', latest: {}}],
  144 + hasNext: false,
  145 + totalElements: 1,
  146 + totalPages: 1
  147 + };
  148 + this.onPageData(pageData);
  149 + }
  150 + }
  151 +
  152 + private resetData() {
  153 + this.alarmIdToDataIndex = {};
  154 + for (let dataIndex = 0; dataIndex < this.pageData.data.length; dataIndex++) {
  155 + const alarmData = this.pageData.data[dataIndex];
  156 + this.alarmIdToDataIndex[alarmData.id.id] = dataIndex;
  157 + }
  158 + }
  159 +
  160 + private onPageData(pageData: PageData<AlarmData>) {
  161 + this.pageData = pageData;
  162 + this.resetData();
  163 + this.listener.alarmsLoaded(pageData, this.alarmDataSubscriptionOptions.pageLink);
  164 + }
  165 +
  166 + private onDataUpdate(update: Array<AlarmData>) {
  167 + for (const alarmData of update) {
  168 + const dataIndex = this.alarmIdToDataIndex[alarmData.id.id];
  169 + if (isDefined(dataIndex) && dataIndex >= 0) {
  170 + this.pageData.data[dataIndex] = alarmData;
  171 + }
  172 + }
  173 + this.listener.alarmsUpdated(update, this.pageData);
  174 + }
  175 +
  176 +}
  1 +///
  2 +/// Copyright © 2016-2020 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { SubscriptionTimewindow } from '@shared/models/time/time.models';
  18 +import { Datasource, DatasourceType } from '@shared/models/widget.models';
  19 +import { PageData } from '@shared/models/page/page-data';
  20 +import { AlarmData, AlarmDataPageLink, KeyFilter } from '@shared/models/query/query.models';
  21 +import { Injectable } from '@angular/core';
  22 +import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service';
  23 +import { UtilsService } from '@core/services/utils.service';
  24 +import {
  25 + AlarmDataSubscription,
  26 + AlarmDataSubscriptionOptions,
  27 + AlarmSubscriptionDataKey
  28 +} from '@core/api/alarm-data-subscription';
  29 +import { deepClone } from '@core/utils';
  30 +
  31 +export interface AlarmDataListener {
  32 + subscriptionTimewindow?: SubscriptionTimewindow;
  33 + alarmSource: Datasource;
  34 + alarmsLoaded: (pageData: PageData<AlarmData>, pageLink: AlarmDataPageLink) => void;
  35 + alarmsUpdated: (update: Array<AlarmData>, pageData: PageData<AlarmData>) => void;
  36 + subscription?: AlarmDataSubscription;
  37 +}
  38 +
  39 +@Injectable({
  40 + providedIn: 'root'
  41 +})
  42 +export class AlarmDataService {
  43 +
  44 + constructor(private telemetryService: TelemetryWebsocketService,
  45 + private utils: UtilsService) {}
  46 +
  47 +
  48 + public subscribeForAlarms(listener: AlarmDataListener,
  49 + pageLink: AlarmDataPageLink,
  50 + keyFilters: KeyFilter[]) {
  51 + const alarmSource = listener.alarmSource;
  52 + if (alarmSource.type === DatasourceType.entity && (!alarmSource.entityFilter || !pageLink)) {
  53 + return;
  54 + }
  55 + listener.subscription = this.createSubscription(listener,
  56 + pageLink, alarmSource.keyFilters, keyFilters);
  57 + return listener.subscription.subscribe();
  58 + }
  59 +
  60 + public stopSubscription(listener: AlarmDataListener) {
  61 + if (listener.subscription) {
  62 + listener.subscription.unsubscribe();
  63 + }
  64 + }
  65 +
  66 + private createSubscription(listener: AlarmDataListener,
  67 + pageLink: AlarmDataPageLink,
  68 + keyFilters: KeyFilter[],
  69 + additionalKeyFilters: KeyFilter[]): AlarmDataSubscription {
  70 + const alarmSource = listener.alarmSource;
  71 + const alarmSubscriptionDataKeys: Array<AlarmSubscriptionDataKey> = [];
  72 + alarmSource.dataKeys.forEach((dataKey) => {
  73 + const alarmSubscriptionDataKey: AlarmSubscriptionDataKey = {
  74 + name: dataKey.name,
  75 + type: dataKey.type
  76 + };
  77 + alarmSubscriptionDataKeys.push(alarmSubscriptionDataKey);
  78 + });
  79 + const alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions = {
  80 + datasourceType: alarmSource.type,
  81 + dataKeys: alarmSubscriptionDataKeys,
  82 + subscriptionTimewindow: deepClone(listener.subscriptionTimewindow)
  83 + };
  84 + if (alarmDataSubscriptionOptions.datasourceType === DatasourceType.entity) {
  85 + alarmDataSubscriptionOptions.entityFilter = alarmSource.entityFilter;
  86 + alarmDataSubscriptionOptions.pageLink = pageLink;
  87 + alarmDataSubscriptionOptions.keyFilters = keyFilters;
  88 + alarmDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters;
  89 + }
  90 + return new AlarmDataSubscription(alarmDataSubscriptionOptions,
  91 + listener, this.telemetryService, this.utils);
  92 + }
  93 +
  94 +}
@@ -41,6 +41,7 @@ import { EntityInfo } from '@app/shared/models/entity.models'; @@ -41,6 +41,7 @@ import { EntityInfo } from '@app/shared/models/entity.models';
41 import { IDashboardComponent } from '@home/models/dashboard-component.models'; 41 import { IDashboardComponent } from '@home/models/dashboard-component.models';
42 import * as moment_ from 'moment'; 42 import * as moment_ from 'moment';
43 import { 43 import {
  44 + AlarmData, AlarmDataPageLink,
44 EntityData, 45 EntityData,
45 EntityDataPageLink, 46 EntityDataPageLink,
46 EntityFilter, 47 EntityFilter,
@@ -51,6 +52,7 @@ import { @@ -51,6 +52,7 @@ import {
51 import { EntityDataService } from '@core/api/entity-data.service'; 52 import { EntityDataService } from '@core/api/entity-data.service';
52 import { PageData } from '@shared/models/page/page-data'; 53 import { PageData } from '@shared/models/page/page-data';
53 import { TranslateService } from '@ngx-translate/core'; 54 import { TranslateService } from '@ngx-translate/core';
  55 +import { AlarmDataService } from '@core/api/alarm-data.service';
54 56
55 export interface TimewindowFunctions { 57 export interface TimewindowFunctions {
56 onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void; 58 onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void;
@@ -184,9 +186,9 @@ export class WidgetSubscriptionContext { @@ -184,9 +186,9 @@ export class WidgetSubscriptionContext {
184 186
185 timeService: TimeService; 187 timeService: TimeService;
186 deviceService: DeviceService; 188 deviceService: DeviceService;
187 - alarmService: AlarmService;  
188 translate: TranslateService; 189 translate: TranslateService;
189 entityDataService: EntityDataService; 190 entityDataService: EntityDataService;
  191 + alarmDataService: AlarmDataService;
190 utils: UtilsService; 192 utils: UtilsService;
191 raf: RafService; 193 raf: RafService;
192 widgetUtils: IWidgetUtils; 194 widgetUtils: IWidgetUtils;
@@ -218,10 +220,10 @@ export interface WidgetSubscriptionOptions { @@ -218,10 +220,10 @@ export interface WidgetSubscriptionOptions {
218 type?: widgetType; 220 type?: widgetType;
219 stateData?: boolean; 221 stateData?: boolean;
220 alarmSource?: Datasource; 222 alarmSource?: Datasource;
221 - alarmSearchStatus?: AlarmSearchStatus; 223 +/* alarmSearchStatus?: AlarmSearchStatus;
222 alarmsPollingInterval?: number; 224 alarmsPollingInterval?: number;
223 alarmsMaxCountLoad?: number; 225 alarmsMaxCountLoad?: number;
224 - alarmsFetchSize?: number; 226 + alarmsFetchSize?: number; */
225 datasources?: Array<Datasource>; 227 datasources?: Array<Datasource>;
226 hasDataPageLink?: boolean; 228 hasDataPageLink?: boolean;
227 singleEntity?: boolean; 229 singleEntity?: boolean;
@@ -269,10 +271,10 @@ export interface IWidgetSubscription { @@ -269,10 +271,10 @@ export interface IWidgetSubscription {
269 timeWindow?: WidgetTimewindow; 271 timeWindow?: WidgetTimewindow;
270 comparisonTimeWindow?: WidgetTimewindow; 272 comparisonTimeWindow?: WidgetTimewindow;
271 273
272 - alarms?: Array<AlarmInfo>; 274 + alarms?: PageData<AlarmData>;
273 alarmSource?: Datasource; 275 alarmSource?: Datasource;
274 - alarmSearchStatus?: AlarmSearchStatus;  
275 - alarmsPollingInterval?: number; 276 + /* alarmSearchStatus?: AlarmSearchStatus;
  277 + alarmsPollingInterval?: number; */
276 278
277 targetDeviceAliasIds?: Array<string>; 279 targetDeviceAliasIds?: Array<string>;
278 targetDeviceIds?: Array<string>; 280 targetDeviceIds?: Array<string>;
@@ -309,6 +311,9 @@ export interface IWidgetSubscription { @@ -309,6 +311,9 @@ export interface IWidgetSubscription {
309 pageLink: EntityDataPageLink, 311 pageLink: EntityDataPageLink,
310 keyFilters: KeyFilter[]): Observable<any>; 312 keyFilters: KeyFilter[]): Observable<any>;
311 313
  314 + subscribeForAlarms(pageLink: AlarmDataPageLink,
  315 + keyFilters: KeyFilter[]): void;
  316 +
312 isDataResolved(): boolean; 317 isDataResolved(): boolean;
313 318
314 destroy(): void; 319 destroy(): void;
@@ -47,21 +47,23 @@ import { @@ -47,21 +47,23 @@ import {
47 import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs'; 47 import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';
48 import { CancelAnimationFrame } from '@core/services/raf.service'; 48 import { CancelAnimationFrame } from '@core/services/raf.service';
49 import { EntityType } from '@shared/models/entity-type.models'; 49 import { EntityType } from '@shared/models/entity-type.models';
50 -import { AlarmInfo, AlarmSearchStatus } from '@shared/models/alarm.models';  
51 import { createLabelFromDatasource, deepClone, isDefined, isEqual } from '@core/utils'; 50 import { createLabelFromDatasource, deepClone, isDefined, isEqual } from '@core/utils';
52 -import { AlarmSourceListener } from '@core/http/alarm.service';  
53 import { EntityId } from '@app/shared/models/id/entity-id'; 51 import { EntityId } from '@app/shared/models/id/entity-id';
54 import * as moment_ from 'moment'; 52 import * as moment_ from 'moment';
55 -import { PageData } from '@shared/models/page/page-data'; 53 +import { emptyPageData, PageData } from '@shared/models/page/page-data';
56 import { EntityDataListener } from '@core/api/entity-data.service'; 54 import { EntityDataListener } from '@core/api/entity-data.service';
57 import { 55 import {
  56 + AlarmData,
  57 + AlarmDataPageLink,
58 EntityData, 58 EntityData,
59 EntityDataPageLink, 59 EntityDataPageLink,
60 entityDataToEntityInfo, 60 entityDataToEntityInfo,
  61 + EntityKeyType,
61 KeyFilter, 62 KeyFilter,
62 updateDatasourceFromEntityInfo 63 updateDatasourceFromEntityInfo
63 } from '@shared/models/query/query.models'; 64 } from '@shared/models/query/query.models';
64 import { map } from 'rxjs/operators'; 65 import { map } from 'rxjs/operators';
  66 +import { AlarmDataListener } from '@core/api/alarm-data.service';
65 67
66 const moment = moment_; 68 const moment = moment_;
67 69
@@ -102,10 +104,11 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -102,10 +104,11 @@ export class WidgetSubscription implements IWidgetSubscription {
102 comparisonTimeWindow: WidgetTimewindow; 104 comparisonTimeWindow: WidgetTimewindow;
103 timewindowForComparison: SubscriptionTimewindow; 105 timewindowForComparison: SubscriptionTimewindow;
104 106
105 - alarms: Array<AlarmInfo>; 107 + // alarms: Array<AlarmInfo>;
  108 + alarms: PageData<AlarmData>;
106 alarmSource: Datasource; 109 alarmSource: Datasource;
107 110
108 - private alarmSearchStatusValue: AlarmSearchStatus; 111 + /* private alarmSearchStatusValue: AlarmSearchStatus;
109 112
110 set alarmSearchStatus(value: AlarmSearchStatus) { 113 set alarmSearchStatus(value: AlarmSearchStatus) {
111 if (this.alarmSearchStatusValue !== value) { 114 if (this.alarmSearchStatusValue !== value) {
@@ -116,12 +119,14 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -116,12 +119,14 @@ export class WidgetSubscription implements IWidgetSubscription {
116 119
117 get alarmSearchStatus(): AlarmSearchStatus { 120 get alarmSearchStatus(): AlarmSearchStatus {
118 return this.alarmSearchStatusValue; 121 return this.alarmSearchStatusValue;
119 - } 122 + }*/
  123 +
  124 + alarmDataListener: AlarmDataListener;
120 125
121 - alarmsPollingInterval: number; 126 +/* alarmsPollingInterval: number;
122 alarmsMaxCountLoad: number; 127 alarmsMaxCountLoad: number;
123 alarmsFetchSize: number; 128 alarmsFetchSize: number;
124 - alarmSourceListener: AlarmSourceListener; 129 + alarmSourceListener: AlarmSourceListener;*/
125 130
126 loadingData: boolean; 131 loadingData: boolean;
127 132
@@ -181,7 +186,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -181,7 +186,7 @@ export class WidgetSubscription implements IWidgetSubscription {
181 this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {}); 186 this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {});
182 this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {}); 187 this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {});
183 this.alarmSource = options.alarmSource; 188 this.alarmSource = options.alarmSource;
184 - this.alarmSearchStatusValue = isDefined(options.alarmSearchStatus) ? 189 + /*this.alarmSearchStatusValue = isDefined(options.alarmSearchStatus) ?
185 options.alarmSearchStatus : AlarmSearchStatus.ANY; 190 options.alarmSearchStatus : AlarmSearchStatus.ANY;
186 this.alarmsPollingInterval = isDefined(options.alarmsPollingInterval) ? 191 this.alarmsPollingInterval = isDefined(options.alarmsPollingInterval) ?
187 options.alarmsPollingInterval : 5000; 192 options.alarmsPollingInterval : 5000;
@@ -189,8 +194,10 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -189,8 +194,10 @@ export class WidgetSubscription implements IWidgetSubscription {
189 options.alarmsMaxCountLoad : 0; 194 options.alarmsMaxCountLoad : 0;
190 this.alarmsFetchSize = isDefined(options.alarmsFetchSize) ? 195 this.alarmsFetchSize = isDefined(options.alarmsFetchSize) ?
191 options.alarmsFetchSize : 100; 196 options.alarmsFetchSize : 100;
192 - this.alarmSourceListener = null;  
193 - this.alarms = []; 197 + this.alarmSourceListener = null;*/
  198 + this.alarmDataListener = null;
  199 + // this.alarms = [];
  200 + this.alarms = emptyPageData();
194 this.originalTimewindow = null; 201 this.originalTimewindow = null;
195 this.timeWindow = {}; 202 this.timeWindow = {};
196 this.useDashboardTimewindow = options.useDashboardTimewindow; 203 this.useDashboardTimewindow = options.useDashboardTimewindow;
@@ -290,7 +297,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -290,7 +297,7 @@ export class WidgetSubscription implements IWidgetSubscription {
290 if (this.targetDeviceId) { 297 if (this.targetDeviceId) {
291 this.rpcEnabled = true; 298 this.rpcEnabled = true;
292 } else { 299 } else {
293 - this.rpcEnabled = this.ctx.utils.widgetEditMode ? true : false; 300 + this.rpcEnabled = this.ctx.utils.widgetEditMode;
294 } 301 }
295 this.hasResolvedData = this.rpcEnabled; 302 this.hasResolvedData = this.rpcEnabled;
296 this.callbacks.rpcStateChanged(this); 303 this.callbacks.rpcStateChanged(this);
@@ -317,7 +324,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -317,7 +324,7 @@ export class WidgetSubscription implements IWidgetSubscription {
317 if (this.targetDeviceId) { 324 if (this.targetDeviceId) {
318 this.rpcEnabled = true; 325 this.rpcEnabled = true;
319 } else { 326 } else {
320 - this.rpcEnabled = this.ctx.utils.widgetEditMode ? true : false; 327 + this.rpcEnabled = this.ctx.utils.widgetEditMode;
321 } 328 }
322 this.hasResolvedData = true; 329 this.hasResolvedData = true;
323 this.callbacks.rpcStateChanged(this); 330 this.callbacks.rpcStateChanged(this);
@@ -356,6 +363,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -356,6 +363,7 @@ export class WidgetSubscription implements IWidgetSubscription {
356 } 363 }
357 364
358 private configureAlarmsData() { 365 private configureAlarmsData() {
  366 + this.notifyDataLoaded();
359 } 367 }
360 368
361 private initDataSubscription(): Observable<any> { 369 private initDataSubscription(): Observable<any> {
@@ -482,13 +490,17 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -482,13 +490,17 @@ export class WidgetSubscription implements IWidgetSubscription {
482 entityName = this.targetDeviceName; 490 entityName = this.targetDeviceName;
483 } 491 }
484 } else if (this.type === widgetType.alarm) { 492 } else if (this.type === widgetType.alarm) {
485 - if (this.alarmSource && this.alarmSource.entityType && this.alarmSource.entityId) {  
486 - entityId = {  
487 - entityType: this.alarmSource.entityType,  
488 - id: this.alarmSource.entityId  
489 - };  
490 - entityName = this.alarmSource.entityName;  
491 - entityLabel = this.alarmSource.entityLabel; 493 + if (this.alarms && this.alarms.data.length) {
  494 + const data = this.alarms.data[0];
  495 + entityId = data.originator;
  496 + entityName = data.originatorName;
  497 + if (data.latest && data.latest[EntityKeyType.ENTITY_FIELD]) {
  498 + const entityFields = data.latest[EntityKeyType.ENTITY_FIELD];
  499 + const labelValue = entityFields.label;
  500 + if (labelValue) {
  501 + entityLabel = labelValue.value;
  502 + }
  503 + }
492 } 504 }
493 } else { 505 } else {
494 for (const datasource of this.datasources) { 506 for (const datasource of this.datasources) {
@@ -522,7 +534,6 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -522,7 +534,6 @@ export class WidgetSubscription implements IWidgetSubscription {
522 } else { 534 } else {
523 return this.checkSubscriptions(aliasIds); 535 return this.checkSubscriptions(aliasIds);
524 } 536 }
525 - return false;  
526 } 537 }
527 538
528 onFiltersChanged(filterIds: Array<string>): boolean { 539 onFiltersChanged(filterIds: Array<string>): boolean {
@@ -573,12 +584,6 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -573,12 +584,6 @@ export class WidgetSubscription implements IWidgetSubscription {
573 return false; 584 return false;
574 } 585 }
575 586
576 - private onAlarmSearchStatusChanged() {  
577 - if (this.type === widgetType.alarm) {  
578 - this.update();  
579 - }  
580 - }  
581 -  
582 updateDataVisibility(index: number): void { 587 updateDataVisibility(index: number): void {
583 if (this.displayLegend) { 588 if (this.displayLegend) {
584 const hidden = this.legendData.keys[index].dataKey.hidden; 589 const hidden = this.legendData.keys[index].dataKey.hidden;
@@ -752,11 +757,12 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -752,11 +757,12 @@ export class WidgetSubscription implements IWidgetSubscription {
752 } 757 }
753 758
754 update() { 759 update() {
755 - if (this.type === widgetType.rpc || this.type === widgetType.alarm) {  
756 - this.unsubscribe();  
757 - this.subscribe();  
758 - } else {  
759 - this.dataSubscribe(); 760 + if (this.type !== widgetType.rpc) {
  761 + if (this.type === widgetType.alarm) {
  762 + this.updateAlarmDataSubscription();
  763 + } else {
  764 + this.dataSubscribe();
  765 + }
760 } 766 }
761 } 767 }
762 768
@@ -821,13 +827,41 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -821,13 +827,41 @@ export class WidgetSubscription implements IWidgetSubscription {
821 } 827 }
822 } 828 }
823 829
824 - private doSubscribe() {  
825 - if (this.type === widgetType.rpc) {  
826 - return; 830 + subscribeForAlarms(pageLink: AlarmDataPageLink,
  831 + keyFilters: KeyFilter[]) {
  832 + if (this.alarmDataListener) {
  833 + this.ctx.alarmDataService.stopSubscription(this.alarmDataListener);
827 } 834 }
828 - if (this.type === widgetType.alarm) {  
829 - this.alarmsSubscribe();  
830 - } else { 835 + if (this.timeWindowConfig) {
  836 + this.updateRealtimeSubscription();
  837 + if (this.subscriptionTimewindow.fixedWindow) {
  838 + this.onDataUpdated();
  839 + }
  840 + }
  841 + this.alarmDataListener = {
  842 + subscriptionTimewindow: this.subscriptionTimewindow,
  843 + alarmSource: this.alarmSource,
  844 + alarmsLoaded: this.alarmsLoaded.bind(this),
  845 + alarmsUpdated: this.alarmsUpdated.bind(this)
  846 + };
  847 +
  848 + this.alarms = emptyPageData();
  849 +
  850 + this.ctx.alarmDataService.subscribeForAlarms(this.alarmDataListener, pageLink, keyFilters);
  851 +
  852 + let forceUpdate = false;
  853 + if (this.alarmSource.unresolvedStateEntity ||
  854 + (this.alarmSource.type === DatasourceType.entity && !this.alarmSource.entityId)
  855 + ) {
  856 + forceUpdate = true;
  857 + }
  858 + if (forceUpdate) {
  859 + this.onDataUpdated();
  860 + }
  861 + }
  862 +
  863 + private doSubscribe() {
  864 + if (this.type !== widgetType.rpc && this.type !== widgetType.alarm) {
831 this.dataSubscribe(); 865 this.dataSubscribe();
832 } 866 }
833 } 867 }
@@ -858,7 +892,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -858,7 +892,7 @@ export class WidgetSubscription implements IWidgetSubscription {
858 } 892 }
859 } 893 }
860 894
861 - private alarmsSubscribe() { 895 + /* private alarmsSubscribe() {
862 this.notifyDataLoading(); 896 this.notifyDataLoading();
863 if (this.timeWindowConfig) { 897 if (this.timeWindowConfig) {
864 this.updateRealtimeSubscription(); 898 this.updateRealtimeSubscription();
@@ -875,9 +909,10 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -875,9 +909,10 @@ export class WidgetSubscription implements IWidgetSubscription {
875 alarmsFetchSize: this.alarmsFetchSize, 909 alarmsFetchSize: this.alarmsFetchSize,
876 alarmsUpdated: alarms => this.alarmsUpdated(alarms) 910 alarmsUpdated: alarms => this.alarmsUpdated(alarms)
877 }; 911 };
878 - this.alarms = null;  
879 912
880 - this.ctx.alarmService.subscribeForAlarms(this.alarmSourceListener); 913 + this.alarms = emptyPageData();
  914 +
  915 + this.ctx.alarmDataService.subscribeForAlarms(this.alarmDataListener);
881 916
882 let forceUpdate = false; 917 let forceUpdate = false;
883 if (this.alarmSource.unresolvedStateEntity || 918 if (this.alarmSource.unresolvedStateEntity ||
@@ -889,7 +924,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -889,7 +924,7 @@ export class WidgetSubscription implements IWidgetSubscription {
889 this.notifyDataLoaded(); 924 this.notifyDataLoaded();
890 this.onDataUpdated(); 925 this.onDataUpdated();
891 } 926 }
892 - } 927 + } */
893 928
894 929
895 unsubscribe() { 930 unsubscribe() {
@@ -910,33 +945,62 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -910,33 +945,62 @@ export class WidgetSubscription implements IWidgetSubscription {
910 } 945 }
911 946
912 private alarmsUnsubscribe() { 947 private alarmsUnsubscribe() {
913 - if (this.alarmSourceListener) {  
914 - this.ctx.alarmService.unsubscribeFromAlarms(this.alarmSourceListener);  
915 - this.alarmSourceListener = null; 948 + if (this.alarmDataListener) {
  949 + this.ctx.alarmDataService.stopSubscription(this.alarmDataListener);
  950 + this.alarmDataListener = null;
916 } 951 }
917 } 952 }
918 953
919 private checkRpcTarget(aliasIds: Array<string>): boolean { 954 private checkRpcTarget(aliasIds: Array<string>): boolean {
920 - if (aliasIds.indexOf(this.targetDeviceAliasId) > -1) {  
921 - return true;  
922 - } else {  
923 - return false;  
924 - } 955 + return aliasIds.indexOf(this.targetDeviceAliasId) > -1;
925 } 956 }
926 957
927 private checkAlarmSource(aliasIds: Array<string>): boolean { 958 private checkAlarmSource(aliasIds: Array<string>): boolean {
928 if (this.options.alarmSource && this.options.alarmSource.entityAliasId) { 959 if (this.options.alarmSource && this.options.alarmSource.entityAliasId) {
929 - return aliasIds.indexOf(this.options.alarmSource.entityAliasId) > -1;  
930 - } else {  
931 - return false; 960 + if (aliasIds.indexOf(this.options.alarmSource.entityAliasId) > -1) {
  961 + this.updateAlarmSubscription();
  962 + }
932 } 963 }
  964 + return false;
933 } 965 }
934 966
935 private checkAlarmSourceFilters(filterIds: Array<string>): boolean { 967 private checkAlarmSourceFilters(filterIds: Array<string>): boolean {
936 if (this.options.alarmSource && this.options.alarmSource.filterId) { 968 if (this.options.alarmSource && this.options.alarmSource.filterId) {
937 - return filterIds.indexOf(this.options.alarmSource.filterId) > -1; 969 + if (filterIds.indexOf(this.options.alarmSource.filterId) > -1) {
  970 + this.updateAlarmSubscription();
  971 + }
  972 + }
  973 + return false;
  974 + }
  975 +
  976 + private updateAlarmSubscription() {
  977 + this.alarmSource = this.options.alarmSource;
  978 + if (!this.ctx.aliasController) {
  979 + this.hasResolvedData = true;
  980 + this.configureAlarmsData();
  981 + this.updateAlarmDataSubscription();
938 } else { 982 } else {
939 - return false; 983 + this.ctx.aliasController.resolveAlarmSource(this.alarmSource).subscribe(
  984 + (alarmSource) => {
  985 + this.alarmSource = alarmSource;
  986 + if (alarmSource) {
  987 + this.hasResolvedData = true;
  988 + }
  989 + this.configureAlarmsData();
  990 + this.updateAlarmDataSubscription();
  991 + },
  992 + () => {
  993 + this.notifyDataLoaded();
  994 + }
  995 + );
  996 + }
  997 + }
  998 +
  999 + private updateAlarmDataSubscription() {
  1000 + if (this.alarmDataListener) {
  1001 + const pageLink = this.alarmDataListener.subscription.alarmDataSubscriptionOptions.pageLink;
  1002 + const keyFilters = this.alarmDataListener.subscription.alarmDataSubscriptionOptions.additionalKeyFilters;
  1003 + this.subscribeForAlarms(pageLink, keyFilters);
940 } 1004 }
941 } 1005 }
942 1006
@@ -999,7 +1063,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -999,7 +1063,7 @@ export class WidgetSubscription implements IWidgetSubscription {
999 } 1063 }
1000 ); 1064 );
1001 }, 1065 },
1002 - (err) => { 1066 + () => {
1003 this.notifyDataLoaded(); 1067 this.notifyDataLoaded();
1004 } 1068 }
1005 ); 1069 );
@@ -1031,11 +1095,6 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1031,11 +1095,6 @@ export class WidgetSubscription implements IWidgetSubscription {
1031 } 1095 }
1032 } 1096 }
1033 1097
1034 - private notifyDataLoading() {  
1035 - this.loadingData = true;  
1036 - this.callbacks.dataLoading(this);  
1037 - }  
1038 -  
1039 private notifyDataLoaded() { 1098 private notifyDataLoaded() {
1040 this.loadingData = false; 1099 this.loadingData = false;
1041 this.callbacks.dataLoading(this); 1100 this.callbacks.dataLoading(this);
@@ -1100,23 +1159,21 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1100,23 +1159,21 @@ export class WidgetSubscription implements IWidgetSubscription {
1100 const datasources = pageData.data.map((entityData, index) => 1159 const datasources = pageData.data.map((entityData, index) =>
1101 this.entityDataToDatasource(datasource, entityData, index) 1160 this.entityDataToDatasource(datasource, entityData, index)
1102 ); 1161 );
1103 - const datasourcesPage: PageData<Datasource> = { 1162 + this.datasourcePages[datasourceIndex] = {
1104 data: datasources, 1163 data: datasources,
1105 hasNext: pageData.hasNext, 1164 hasNext: pageData.hasNext,
1106 totalElements: pageData.totalElements, 1165 totalElements: pageData.totalElements,
1107 totalPages: pageData.totalPages 1166 totalPages: pageData.totalPages
1108 }; 1167 };
1109 - this.datasourcePages[datasourceIndex] = datasourcesPage;  
1110 const datasourceData = datasources.map((datasourceElement, index) => 1168 const datasourceData = datasources.map((datasourceElement, index) =>
1111 this.entityDataToDatasourceData(datasourceElement, data[index]) 1169 this.entityDataToDatasourceData(datasourceElement, data[index])
1112 ); 1170 );
1113 - const datasourceDataPage: PageData<Array<DatasourceData>> = { 1171 + this.dataPages[datasourceIndex] = {
1114 data: datasourceData, 1172 data: datasourceData,
1115 hasNext: pageData.hasNext, 1173 hasNext: pageData.hasNext,
1116 totalElements: pageData.totalElements, 1174 totalElements: pageData.totalElements,
1117 totalPages: pageData.totalPages 1175 totalPages: pageData.totalPages
1118 }; 1176 };
1119 - this.dataPages[datasourceIndex] = datasourceDataPage;  
1120 if (datasource.type === DatasourceType.entity && 1177 if (datasource.type === DatasourceType.entity &&
1121 pageData.hasNext && pageLink.pageSize > 1) { 1178 pageData.hasNext && pageLink.pageSize > 1) {
1122 if (this.warnOnPageDataOverflow) { 1179 if (this.warnOnPageDataOverflow) {
@@ -1215,8 +1272,8 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1215,8 +1272,8 @@ export class WidgetSubscription implements IWidgetSubscription {
1215 1272
1216 private entityDataToDatasourceData(datasource: Datasource, data: Array<DataSetHolder>): Array<DatasourceData> { 1273 private entityDataToDatasourceData(datasource: Datasource, data: Array<DataSetHolder>): Array<DatasourceData> {
1217 return datasource.dataKeys.map((dataKey, keyIndex) => { 1274 return datasource.dataKeys.map((dataKey, keyIndex) => {
1218 - dataKey.hidden = dataKey.settings.hideDataByDefault ? true : false;  
1219 - dataKey.inLegend = dataKey.settings.removeFromLegend ? false : true; 1275 + dataKey.hidden = !!dataKey.settings.hideDataByDefault;
  1276 + dataKey.inLegend = !dataKey.settings.removeFromLegend;
1220 if (this.comparisonEnabled && dataKey.isAdditional && dataKey.settings.comparisonSettings.comparisonValuesLabel) { 1277 if (this.comparisonEnabled && dataKey.isAdditional && dataKey.settings.comparisonSettings.comparisonValuesLabel) {
1221 dataKey.label = createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel); 1278 dataKey.label = createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel);
1222 } else { 1279 } else {
@@ -1242,7 +1299,7 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1242,7 +1299,7 @@ export class WidgetSubscription implements IWidgetSubscription {
1242 const newDatasource = deepClone(configDatasource); 1299 const newDatasource = deepClone(configDatasource);
1243 const entityInfo = entityDataToEntityInfo(entityData); 1300 const entityInfo = entityDataToEntityInfo(entityData);
1244 updateDatasourceFromEntityInfo(newDatasource, entityInfo); 1301 updateDatasourceFromEntityInfo(newDatasource, entityInfo);
1245 - newDatasource.generated = index > 0 ? true : false; 1302 + newDatasource.generated = index > 0;
1246 return newDatasource; 1303 return newDatasource;
1247 } 1304 }
1248 1305
@@ -1285,16 +1342,16 @@ export class WidgetSubscription implements IWidgetSubscription { @@ -1285,16 +1342,16 @@ export class WidgetSubscription implements IWidgetSubscription {
1285 } 1342 }
1286 } 1343 }
1287 1344
1288 - private alarmsUpdated(alarms: Array<AlarmInfo>) {  
1289 - this.notifyDataLoaded();  
1290 - const updated = !this.alarms || !isEqual(this.alarms, alarms); 1345 + private alarmsLoaded(alarms: PageData<AlarmData>) {
1291 this.alarms = alarms; 1346 this.alarms = alarms;
1292 if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) { 1347 if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) {
1293 this.updateTimewindow(); 1348 this.updateTimewindow();
1294 } 1349 }
1295 - if (updated) {  
1296 - this.onDataUpdated();  
1297 - } 1350 + this.onDataUpdated();
  1351 + }
  1352 +
  1353 + private alarmsUpdated(_updated: Array<AlarmData>, alarms: PageData<AlarmData>) {
  1354 + this.alarmsLoaded(alarms);
1298 } 1355 }
1299 1356
1300 private updateLegend(dataIndex: number, data: DataSet, detectChanges: boolean) { 1357 private updateLegend(dataIndex: number, data: DataSet, detectChanges: boolean) {
@@ -16,8 +16,10 @@ @@ -16,8 +16,10 @@
16 16
17 import { Inject, Injectable, NgZone } from '@angular/core'; 17 import { Inject, Injectable, NgZone } from '@angular/core';
18 import { 18 import {
  19 + AlarmDataCmd, AlarmDataUnsubscribeCmd,
  20 + AlarmDataUpdate,
19 AttributesSubscriptionCmd, EntityDataCmd, EntityDataUnsubscribeCmd, EntityDataUpdate, 21 AttributesSubscriptionCmd, EntityDataCmd, EntityDataUnsubscribeCmd, EntityDataUpdate,
20 - GetHistoryCmd, isEntityDataUpdateMsg, 22 + GetHistoryCmd, isAlarmDataUpdateMsg, isEntityDataUpdateMsg,
21 SubscriptionCmd, 23 SubscriptionCmd,
22 SubscriptionUpdate, 24 SubscriptionUpdate,
23 SubscriptionUpdateMsg, 25 SubscriptionUpdateMsg,
@@ -107,6 +109,8 @@ export class TelemetryWebsocketService implements TelemetryService { @@ -107,6 +109,8 @@ export class TelemetryWebsocketService implements TelemetryService {
107 this.cmdsWrapper.historyCmds.push(subscriptionCommand); 109 this.cmdsWrapper.historyCmds.push(subscriptionCommand);
108 } else if (subscriptionCommand instanceof EntityDataCmd) { 110 } else if (subscriptionCommand instanceof EntityDataCmd) {
109 this.cmdsWrapper.entityDataCmds.push(subscriptionCommand); 111 this.cmdsWrapper.entityDataCmds.push(subscriptionCommand);
  112 + } else if (subscriptionCommand instanceof AlarmDataCmd) {
  113 + this.cmdsWrapper.alarmDataCmds.push(subscriptionCommand);
110 } 114 }
111 } 115 }
112 ); 116 );
@@ -142,6 +146,10 @@ export class TelemetryWebsocketService implements TelemetryService { @@ -142,6 +146,10 @@ export class TelemetryWebsocketService implements TelemetryService {
142 const entityDataUnsubscribeCmd = new EntityDataUnsubscribeCmd(); 146 const entityDataUnsubscribeCmd = new EntityDataUnsubscribeCmd();
143 entityDataUnsubscribeCmd.cmdId = subscriptionCommand.cmdId; 147 entityDataUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
144 this.cmdsWrapper.entityDataUnsubscribeCmds.push(entityDataUnsubscribeCmd); 148 this.cmdsWrapper.entityDataUnsubscribeCmds.push(entityDataUnsubscribeCmd);
  149 + } else if (subscriptionCommand instanceof AlarmDataCmd) {
  150 + const alarmDataUnsubscribeCmd = new AlarmDataUnsubscribeCmd();
  151 + alarmDataUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
  152 + this.cmdsWrapper.alarmDataUnsubscribeCmds.push(alarmDataUnsubscribeCmd);
145 } 153 }
146 const cmdId = subscriptionCommand.cmdId; 154 const cmdId = subscriptionCommand.cmdId;
147 if (cmdId) { 155 if (cmdId) {
@@ -281,6 +289,11 @@ export class TelemetryWebsocketService implements TelemetryService { @@ -281,6 +289,11 @@ export class TelemetryWebsocketService implements TelemetryService {
281 if (subscriber) { 289 if (subscriber) {
282 subscriber.onEntityData(new EntityDataUpdate(message)); 290 subscriber.onEntityData(new EntityDataUpdate(message));
283 } 291 }
  292 + } else if (isAlarmDataUpdateMsg(message)) {
  293 + subscriber = this.subscribersMap.get(message.cmdId);
  294 + if (subscriber) {
  295 + subscriber.onAlarmData(new AlarmDataUpdate(message));
  296 + }
284 } else if (message.subscriptionId) { 297 } else if (message.subscriptionId) {
285 subscriber = this.subscribersMap.get(message.subscriptionId); 298 subscriber = this.subscribersMap.get(message.subscriptionId);
286 if (subscriber) { 299 if (subscriber) {
@@ -62,7 +62,7 @@ @@ -62,7 +62,7 @@
62 </mat-toolbar> 62 </mat-toolbar>
63 <div fxFlex class="table-container"> 63 <div fxFlex class="table-container">
64 <table mat-table [dataSource]="alarmsDatasource" 64 <table mat-table [dataSource]="alarmsDatasource"
65 - matSort [matSortActive]="sortOrderProperty" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear> 65 + matSort [matSortActive]="sortOrderProperty" [matSortDirection]="pageLinkSortDirection()" matSortDisableClear>
66 <ng-container matColumnDef="select" sticky> 66 <ng-container matColumnDef="select" sticky>
67 <mat-header-cell *matHeaderCellDef style="width: 30px;"> 67 <mat-header-cell *matHeaderCellDef style="width: 30px;">
68 <mat-checkbox (change)="$event ? alarmsDatasource.masterToggle() : null" 68 <mat-checkbox (change)="$event ? alarmsDatasource.masterToggle() : null"
@@ -125,9 +125,12 @@ @@ -125,9 +125,12 @@
125 *matRowDef="let alarm; columns: displayedColumns;" 125 *matRowDef="let alarm; columns: displayedColumns;"
126 (click)="onRowClick($event, alarm)"></mat-row> 126 (click)="onRowClick($event, alarm)"></mat-row>
127 </table> 127 </table>
128 - <span [fxShow]="alarmsDatasource.isEmpty() | async" 128 + <span [fxShow]="(alarmsDatasource.isEmpty() | async) && !alarmsDatasource.dataLoading"
129 fxLayoutAlign="center center" 129 fxLayoutAlign="center center"
130 class="no-data-found" translate>alarm.no-alarms-prompt</span> 130 class="no-data-found" translate>alarm.no-alarms-prompt</span>
  131 + <span [fxShow]="alarmsDatasource.dataLoading"
  132 + fxLayoutAlign="center center"
  133 + class="no-data-found">{{ 'common.loading' | translate }}</span>
131 </div> 134 </div>
132 <mat-divider *ngIf="displayPagination"></mat-divider> 135 <mat-divider *ngIf="displayPagination"></mat-divider>
133 <mat-paginator *ngIf="displayPagination" 136 <mat-paginator *ngIf="displayPagination"
@@ -29,21 +29,21 @@ import { PageComponent } from '@shared/components/page.component'; @@ -29,21 +29,21 @@ import { PageComponent } from '@shared/components/page.component';
29 import { Store } from '@ngrx/store'; 29 import { Store } from '@ngrx/store';
30 import { AppState } from '@core/core.state'; 30 import { AppState } from '@core/core.state';
31 import { WidgetAction, WidgetContext } from '@home/models/widget-component.models'; 31 import { WidgetAction, WidgetContext } from '@home/models/widget-component.models';
32 -import { Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models/widget.models'; 32 +import { DataKey, Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models/widget.models';
33 import { IWidgetSubscription } from '@core/api/widget-api.models'; 33 import { IWidgetSubscription } from '@core/api/widget-api.models';
34 import { UtilsService } from '@core/services/utils.service'; 34 import { UtilsService } from '@core/services/utils.service';
35 import { TranslateService } from '@ngx-translate/core'; 35 import { TranslateService } from '@ngx-translate/core';
36 -import { deepClone, isDefined, isNumber, createLabelFromDatasource, hashCode } from '@core/utils'; 36 +import { createLabelFromDatasource, deepClone, hashCode, isDefined, isNumber } from '@core/utils';
37 import cssjs from '@core/css/css'; 37 import cssjs from '@core/css/css';
38 -import { PageLink } from '@shared/models/page/page-link';  
39 -import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; 38 +import { sortItems } from '@shared/models/page/page-link';
  39 +import { Direction } from '@shared/models/page/sort-order';
40 import { CollectionViewer, DataSource, SelectionModel } from '@angular/cdk/collections'; 40 import { CollectionViewer, DataSource, SelectionModel } from '@angular/cdk/collections';
41 -import { BehaviorSubject, forkJoin, fromEvent, merge, Observable, of } from 'rxjs'; 41 +import { BehaviorSubject, forkJoin, fromEvent, merge, Observable } from 'rxjs';
42 import { emptyPageData, PageData } from '@shared/models/page/page-data'; 42 import { emptyPageData, PageData } from '@shared/models/page/page-data';
43 import { entityTypeTranslations } from '@shared/models/entity-type.models'; 43 import { entityTypeTranslations } from '@shared/models/entity-type.models';
44 -import { catchError, debounceTime, distinctUntilChanged, map, take, tap } from 'rxjs/operators'; 44 +import { debounceTime, distinctUntilChanged, map, take, tap } from 'rxjs/operators';
45 import { MatPaginator } from '@angular/material/paginator'; 45 import { MatPaginator } from '@angular/material/paginator';
46 -import { MatSort } from '@angular/material/sort'; 46 +import { MatSort, SortDirection } from '@angular/material/sort';
47 import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; 47 import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
48 import { 48 import {
49 CellContentInfo, 49 CellContentInfo,
@@ -51,14 +51,16 @@ import { @@ -51,14 +51,16 @@ import {
51 constructTableCssString, 51 constructTableCssString,
52 DisplayColumn, 52 DisplayColumn,
53 EntityColumn, 53 EntityColumn,
54 - fromAlarmColumnDef, 54 + entityDataSortOrderFromString,
  55 + findColumnByEntityKey,
  56 + findEntityKeyByColumnDef,
  57 + fromEntityColumnDef,
55 getAlarmValue, 58 getAlarmValue,
56 getCellContentInfo, 59 getCellContentInfo,
57 getCellStyleInfo, 60 getCellStyleInfo,
58 getColumnWidth, 61 getColumnWidth,
59 TableWidgetDataKeySettings, 62 TableWidgetDataKeySettings,
60 TableWidgetSettings, 63 TableWidgetSettings,
61 - toAlarmColumnDef,  
62 widthStyle 64 widthStyle
63 } from '@home/components/widget/lib/table-widget.models'; 65 } from '@home/components/widget/lib/table-widget.models';
64 import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay'; 66 import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
@@ -69,8 +71,8 @@ import { @@ -69,8 +71,8 @@ import {
69 DisplayColumnsPanelData 71 DisplayColumnsPanelData
70 } from '@home/components/widget/lib/display-columns-panel.component'; 72 } from '@home/components/widget/lib/display-columns-panel.component';
71 import { 73 import {
  74 + AlarmDataInfo,
72 alarmFields, 75 alarmFields,
73 - AlarmInfo,  
74 alarmSeverityColors, 76 alarmSeverityColors,
75 alarmSeverityTranslations, 77 alarmSeverityTranslations,
76 AlarmStatus, 78 AlarmStatus,
@@ -90,6 +92,15 @@ import { MatDialog } from '@angular/material/dialog'; @@ -90,6 +92,15 @@ import { MatDialog } from '@angular/material/dialog';
90 import { NULL_UUID } from '@shared/models/id/has-uuid'; 92 import { NULL_UUID } from '@shared/models/id/has-uuid';
91 import { DialogService } from '@core/services/dialog.service'; 93 import { DialogService } from '@core/services/dialog.service';
92 import { AlarmService } from '@core/http/alarm.service'; 94 import { AlarmService } from '@core/http/alarm.service';
  95 +import {
  96 + AlarmData,
  97 + AlarmDataPageLink,
  98 + dataKeyToEntityKey,
  99 + dataKeyTypeToEntityKeyType,
  100 + entityDataPageLinkSortDirection,
  101 + KeyFilter
  102 +} from '@app/shared/models/query/query.models';
  103 +import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
93 104
94 interface AlarmsTableWidgetSettings extends TableWidgetSettings { 105 interface AlarmsTableWidgetSettings extends TableWidgetSettings {
95 alarmsTitle: string; 106 alarmsTitle: string;
@@ -125,7 +136,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -125,7 +136,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
125 public displayPagination = true; 136 public displayPagination = true;
126 public enableStickyAction = false; 137 public enableStickyAction = false;
127 public pageSizeOptions; 138 public pageSizeOptions;
128 - public pageLink: PageLink; 139 + public pageLink: AlarmDataPageLink;
129 public sortOrderProperty: string; 140 public sortOrderProperty: string;
130 public textSearchMode = false; 141 public textSearchMode = false;
131 public columns: Array<EntityColumn> = []; 142 public columns: Array<EntityColumn> = [];
@@ -189,9 +200,11 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -189,9 +200,11 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
189 private dialogService: DialogService, 200 private dialogService: DialogService,
190 private alarmService: AlarmService) { 201 private alarmService: AlarmService) {
191 super(store); 202 super(store);
192 -  
193 - const sortOrder: SortOrder = sortOrderFromString(this.defaultSortOrder);  
194 - this.pageLink = new PageLink(this.defaultPageSize, 0, null, sortOrder); 203 + this.pageLink = {
  204 + page: 0,
  205 + pageSize: this.defaultPageSize,
  206 + textSearch: null
  207 + };
195 } 208 }
196 209
197 ngOnInit(): void { 210 ngOnInit(): void {
@@ -232,11 +245,15 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -232,11 +245,15 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
232 245
233 public onDataUpdated() { 246 public onDataUpdated() {
234 this.ngZone.run(() => { 247 this.ngZone.run(() => {
235 - this.alarmsDatasource.updateAlarms(this.subscription.alarms); 248 + this.alarmsDatasource.updateAlarms();
236 this.ctx.detectChanges(); 249 this.ctx.detectChanges();
237 }); 250 });
238 } 251 }
239 252
  253 + public pageLinkSortDirection(): SortDirection {
  254 + return entityDataPageLinkSortDirection(this.pageLink);
  255 + }
  256 +
240 private initializeConfig() { 257 private initializeConfig() {
241 this.ctx.widgetActions = [this.searchAction, this.statusFilterAction, this.columnDisplayAction]; 258 this.ctx.widgetActions = [this.searchAction, this.statusFilterAction, this.columnDisplayAction];
242 259
@@ -304,6 +321,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -304,6 +321,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
304 this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3]; 321 this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];
305 this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY; 322 this.pageLink.pageSize = this.displayPagination ? this.defaultPageSize : Number.POSITIVE_INFINITY;
306 323
  324 + // TODO: search status, severity, types, searchPropagatedAlarms from widget config to pageLink
  325 + this.pageLink.searchPropagatedAlarms = false; // true for old widget configs
  326 + this.pageLink.severityList = [];
  327 + this.pageLink.statusList = [];
  328 + this.pageLink.typeList = [];
  329 +
307 const cssString = constructTableCssString(this.widgetConfig); 330 const cssString = constructTableCssString(this.widgetConfig);
308 const cssParser = new cssjs(); 331 const cssParser = new cssjs();
309 cssParser.testMode = false; 332 cssParser.testMode = false;
@@ -319,9 +342,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -319,9 +342,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
319 this.displayedColumns.push('select'); 342 this.displayedColumns.push('select');
320 } 343 }
321 344
  345 + const latestDataKeys: Array<DataKey> = [];
  346 +
322 if (this.alarmSource) { 347 if (this.alarmSource) {
323 this.alarmSource.dataKeys.forEach((alarmDataKey) => { 348 this.alarmSource.dataKeys.forEach((alarmDataKey) => {
324 const dataKey: EntityColumn = deepClone(alarmDataKey) as EntityColumn; 349 const dataKey: EntityColumn = deepClone(alarmDataKey) as EntityColumn;
  350 + dataKey.entityKey = dataKeyToEntityKey(alarmDataKey);
325 dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label); 351 dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label);
326 dataKey.def = 'def' + this.columns.length; 352 dataKey.def = 'def' + this.columns.length;
327 const keySettings: TableWidgetDataKeySettings = dataKey.settings; 353 const keySettings: TableWidgetDataKeySettings = dataKey.settings;
@@ -330,19 +356,28 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -330,19 +356,28 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
330 this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx'); 356 this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx');
331 this.columnWidth[dataKey.def] = getColumnWidth(keySettings); 357 this.columnWidth[dataKey.def] = getColumnWidth(keySettings);
332 this.columns.push(dataKey); 358 this.columns.push(dataKey);
  359 +
  360 + if (dataKey.type !== DataKeyType.alarm) {
  361 + latestDataKeys.push(dataKey);
  362 + }
333 }); 363 });
334 this.displayedColumns.push(...this.columns.map(column => column.def)); 364 this.displayedColumns.push(...this.columns.map(column => column.def));
335 } 365 }
336 if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) { 366 if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) {
337 this.defaultSortOrder = this.settings.defaultSortOrder; 367 this.defaultSortOrder = this.settings.defaultSortOrder;
338 } 368 }
339 - this.pageLink.sortOrder = sortOrderFromString(this.defaultSortOrder);  
340 - this.sortOrderProperty = toAlarmColumnDef(this.pageLink.sortOrder.property, this.columns); 369 + this.pageLink.sortOrder = entityDataSortOrderFromString(this.defaultSortOrder, this.columns);
  370 + let sortColumn: EntityColumn;
  371 + if (this.pageLink.sortOrder) {
  372 + sortColumn = findColumnByEntityKey(this.pageLink.sortOrder.key, this.columns);
  373 + }
  374 + this.sortOrderProperty = sortColumn ? sortColumn.def : null;
341 375
342 if (this.actionCellDescriptors.length) { 376 if (this.actionCellDescriptors.length) {
343 this.displayedColumns.push('actions'); 377 this.displayedColumns.push('actions');
344 } 378 }
345 - this.alarmsDatasource = new AlarmsDatasource(); 379 +
  380 + this.alarmsDatasource = new AlarmsDatasource(this.subscription, latestDataKeys);
346 if (this.enableSelection) { 381 if (this.enableSelection) {
347 this.alarmsDatasource.selectionModeChanged$.subscribe((selectionMode) => { 382 this.alarmsDatasource.selectionModeChanged$.subscribe((selectionMode) => {
348 const hideTitlePanel = selectionMode || this.textSearchMode; 383 const hideTitlePanel = selectionMode || this.textSearchMode;
@@ -467,9 +502,13 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -467,9 +502,13 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
467 } else { 502 } else {
468 this.pageLink.page = 0; 503 this.pageLink.page = 0;
469 } 504 }
470 - this.pageLink.sortOrder.property = fromAlarmColumnDef(this.sort.active, this.columns);  
471 - this.pageLink.sortOrder.direction = Direction[this.sort.direction.toUpperCase()];  
472 - this.alarmsDatasource.loadAlarms(this.pageLink); 505 + this.pageLink.sortOrder = {
  506 + key: findEntityKeyByColumnDef(this.sort.active, this.columns),
  507 + direction: Direction[this.sort.direction.toUpperCase()]
  508 + };
  509 + const sortOrderLabel = fromEntityColumnDef(this.sort.active, this.columns);
  510 + const keyFilters: KeyFilter[] = null; // TODO:
  511 + this.alarmsDatasource.loadAlarms(this.pageLink, sortOrderLabel, keyFilters);
473 this.ctx.detectChanges(); 512 this.ctx.detectChanges();
474 } 513 }
475 514
@@ -482,7 +521,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -482,7 +521,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
482 return widthStyle(columnWidth); 521 return widthStyle(columnWidth);
483 } 522 }
484 523
485 - public cellStyle(alarm: AlarmInfo, key: EntityColumn): any { 524 + public cellStyle(alarm: AlarmDataInfo, key: EntityColumn): any {
486 let style: any = {}; 525 let style: any = {};
487 if (alarm && key) { 526 if (alarm && key) {
488 const styleInfo = this.stylesInfo[key.def]; 527 const styleInfo = this.stylesInfo[key.def];
@@ -504,7 +543,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -504,7 +543,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
504 return style; 543 return style;
505 } 544 }
506 545
507 - public cellContent(alarm: AlarmInfo, key: EntityColumn): SafeHtml { 546 + public cellContent(alarm: AlarmDataInfo, key: EntityColumn): SafeHtml {
508 if (alarm && key) { 547 if (alarm && key) {
509 const contentInfo = this.contentsInfo[key.def]; 548 const contentInfo = this.contentsInfo[key.def];
510 const value = getAlarmValue(alarm, key); 549 const value = getAlarmValue(alarm, key);
@@ -524,7 +563,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -524,7 +563,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
524 } 563 }
525 } 564 }
526 565
527 - public onRowClick($event: Event, alarm: AlarmInfo) { 566 + public onRowClick($event: Event, alarm: AlarmDataInfo) {
528 if ($event) { 567 if ($event) {
529 $event.stopPropagation(); 568 $event.stopPropagation();
530 } 569 }
@@ -541,7 +580,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -541,7 +580,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
541 } 580 }
542 } 581 }
543 582
544 - public onActionButtonClick($event: Event, alarm: AlarmInfo, actionDescriptor: AlarmWidgetActionDescriptor) { 583 + public onActionButtonClick($event: Event, alarm: AlarmDataInfo, actionDescriptor: AlarmWidgetActionDescriptor) {
545 if (actionDescriptor.details) { 584 if (actionDescriptor.details) {
546 this.openAlarmDetails($event, alarm); 585 this.openAlarmDetails($event, alarm);
547 } else if (actionDescriptor.acknowledge) { 586 } else if (actionDescriptor.acknowledge) {
@@ -562,7 +601,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -562,7 +601,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
562 } 601 }
563 } 602 }
564 603
565 - public actionEnabled(alarm: AlarmInfo, actionDescriptor: AlarmWidgetActionDescriptor): boolean { 604 + public actionEnabled(alarm: AlarmDataInfo, actionDescriptor: AlarmWidgetActionDescriptor): boolean {
566 if (actionDescriptor.acknowledge) { 605 if (actionDescriptor.acknowledge) {
567 return (alarm.status === AlarmStatus.ACTIVE_UNACK || 606 return (alarm.status === AlarmStatus.ACTIVE_UNACK ||
568 alarm.status === AlarmStatus.CLEARED_UNACK); 607 alarm.status === AlarmStatus.CLEARED_UNACK);
@@ -573,7 +612,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -573,7 +612,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
573 return true; 612 return true;
574 } 613 }
575 614
576 - private openAlarmDetails($event: Event, alarm: AlarmInfo) { 615 + private openAlarmDetails($event: Event, alarm: AlarmDataInfo) {
577 if ($event) { 616 if ($event) {
578 $event.stopPropagation(); 617 $event.stopPropagation();
579 } 618 }
@@ -599,7 +638,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -599,7 +638,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
599 } 638 }
600 } 639 }
601 640
602 - private ackAlarm($event: Event, alarm: AlarmInfo) { 641 + private ackAlarm($event: Event, alarm: AlarmDataInfo) {
603 if ($event) { 642 if ($event) {
604 $event.stopPropagation(); 643 $event.stopPropagation();
605 } 644 }
@@ -626,12 +665,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -626,12 +665,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
626 $event.stopPropagation(); 665 $event.stopPropagation();
627 } 666 }
628 if (this.alarmsDatasource.selection.hasValue()) { 667 if (this.alarmsDatasource.selection.hasValue()) {
629 - const alarms = this.alarmsDatasource.selection.selected.filter(  
630 - (alarm) => alarm.id.id !== NULL_UUID 668 + const alarmIds = this.alarmsDatasource.selection.selected.filter(
  669 + (alarmId) => alarmId !== NULL_UUID
631 ); 670 );
632 - if (alarms.length) {  
633 - const title = this.translate.instant('alarm.aknowledge-alarms-title', {count: alarms.length});  
634 - const content = this.translate.instant('alarm.aknowledge-alarms-text', {count: alarms.length}); 671 + if (alarmIds.length) {
  672 + const title = this.translate.instant('alarm.aknowledge-alarms-title', {count: alarmIds.length});
  673 + const content = this.translate.instant('alarm.aknowledge-alarms-text', {count: alarmIds.length});
635 this.dialogService.confirm( 674 this.dialogService.confirm(
636 title, 675 title,
637 content, 676 content,
@@ -641,8 +680,8 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -641,8 +680,8 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
641 if (res) { 680 if (res) {
642 if (res) { 681 if (res) {
643 const tasks: Observable<void>[] = []; 682 const tasks: Observable<void>[] = [];
644 - for (const alarm of alarms) {  
645 - tasks.push(this.alarmService.ackAlarm(alarm.id.id)); 683 + for (const alarmId of alarmIds) {
  684 + tasks.push(this.alarmService.ackAlarm(alarmId));
646 } 685 }
647 forkJoin(tasks).subscribe(() => { 686 forkJoin(tasks).subscribe(() => {
648 this.alarmsDatasource.clearSelection(); 687 this.alarmsDatasource.clearSelection();
@@ -655,7 +694,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -655,7 +694,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
655 } 694 }
656 } 695 }
657 696
658 - private clearAlarm($event: Event, alarm: AlarmInfo) { 697 + private clearAlarm($event: Event, alarm: AlarmDataInfo) {
659 if ($event) { 698 if ($event) {
660 $event.stopPropagation(); 699 $event.stopPropagation();
661 } 700 }
@@ -682,12 +721,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -682,12 +721,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
682 $event.stopPropagation(); 721 $event.stopPropagation();
683 } 722 }
684 if (this.alarmsDatasource.selection.hasValue()) { 723 if (this.alarmsDatasource.selection.hasValue()) {
685 - const alarms = this.alarmsDatasource.selection.selected.filter(  
686 - (alarm) => alarm.id.id !== NULL_UUID 724 + const alarmIds = this.alarmsDatasource.selection.selected.filter(
  725 + (alarmId) => alarmId !== NULL_UUID
687 ); 726 );
688 - if (alarms.length) {  
689 - const title = this.translate.instant('alarm.clear-alarms-title', {count: alarms.length});  
690 - const content = this.translate.instant('alarm.clear-alarms-text', {count: alarms.length}); 727 + if (alarmIds.length) {
  728 + const title = this.translate.instant('alarm.clear-alarms-title', {count: alarmIds.length});
  729 + const content = this.translate.instant('alarm.clear-alarms-text', {count: alarmIds.length});
691 this.dialogService.confirm( 730 this.dialogService.confirm(
692 title, 731 title,
693 content, 732 content,
@@ -697,8 +736,8 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -697,8 +736,8 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
697 if (res) { 736 if (res) {
698 if (res) { 737 if (res) {
699 const tasks: Observable<void>[] = []; 738 const tasks: Observable<void>[] = [];
700 - for (const alarm of alarms) {  
701 - tasks.push(this.alarmService.clearAlarm(alarm.id.id)); 739 + for (const alarmId of alarmIds) {
  740 + tasks.push(this.alarmService.clearAlarm(alarmId));
702 } 741 }
703 forkJoin(tasks).subscribe(() => { 742 forkJoin(tasks).subscribe(() => {
704 this.alarmsDatasource.clearSelection(); 743 this.alarmsDatasource.clearSelection();
@@ -756,27 +795,29 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit, @@ -756,27 +795,29 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
756 795
757 } 796 }
758 797
759 -class AlarmsDatasource implements DataSource<AlarmInfo> { 798 +class AlarmsDatasource implements DataSource<AlarmDataInfo> {
760 799
761 - private alarmsSubject = new BehaviorSubject<AlarmInfo[]>([]);  
762 - private pageDataSubject = new BehaviorSubject<PageData<AlarmInfo>>(emptyPageData<AlarmInfo>()); 800 + private alarmsSubject = new BehaviorSubject<AlarmDataInfo[]>([]);
  801 + private pageDataSubject = new BehaviorSubject<PageData<AlarmDataInfo>>(emptyPageData<AlarmDataInfo>());
763 802
764 - public selection = new SelectionModel<AlarmInfo>(true, [], false); 803 + public selection = new SelectionModel<string>(true, [], false);
765 804
766 private selectionModeChanged = new EventEmitter<boolean>(); 805 private selectionModeChanged = new EventEmitter<boolean>();
767 806
768 - public selectionModeChanged$ = this.selectionModeChanged.asObservable(); 807 + public selectionModeChanged$ = this.selectionModeChanged.asObservable();
  808 +
  809 + private currentAlarm: AlarmDataInfo = null;
769 810
770 - private allAlarms: Array<AlarmInfo> = [];  
771 - private allAlarmsSubject = new BehaviorSubject<AlarmInfo[]>([]);  
772 - private allAlarms$: Observable<Array<AlarmInfo>> = this.allAlarmsSubject.asObservable(); 811 + public dataLoading = true;
773 812
774 - private currentAlarm: AlarmInfo = null; 813 + private appliedPageLink: AlarmDataPageLink;
  814 + private appliedSortOrderLabel: string;
775 815
776 - constructor() { 816 + constructor(private subscription: IWidgetSubscription,
  817 + private dataKeys: Array<DataKey>) {
777 } 818 }
778 819
779 - connect(collectionViewer: CollectionViewer): Observable<AlarmInfo[] | ReadonlyArray<AlarmInfo>> { 820 + connect(collectionViewer: CollectionViewer): Observable<AlarmDataInfo[] | ReadonlyArray<AlarmDataInfo>> {
780 return this.alarmsSubject.asObservable(); 821 return this.alarmsSubject.asObservable();
781 } 822 }
782 823
@@ -785,51 +826,63 @@ class AlarmsDatasource implements DataSource<AlarmInfo> { @@ -785,51 +826,63 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
785 this.pageDataSubject.complete(); 826 this.pageDataSubject.complete();
786 } 827 }
787 828
788 - loadAlarms(pageLink: PageLink) { 829 + loadAlarms(pageLink: AlarmDataPageLink, sortOrderLabel: string, keyFilters: KeyFilter[]) {
789 if (this.selection.hasValue()) { 830 if (this.selection.hasValue()) {
790 this.selection.clear(); 831 this.selection.clear();
791 this.onSelectionModeChanged(false); 832 this.onSelectionModeChanged(false);
792 } 833 }
793 - this.fetchAlarms(pageLink).pipe(  
794 - catchError(() => of(emptyPageData<AlarmInfo>())),  
795 - ).subscribe(  
796 - (pageData) => {  
797 - this.alarmsSubject.next(pageData.data);  
798 - this.pageDataSubject.next(pageData);  
799 - }  
800 - ); 834 + this.appliedPageLink = pageLink;
  835 + this.appliedSortOrderLabel = sortOrderLabel;
  836 + this.subscription.subscribeForAlarms(pageLink, keyFilters);
801 } 837 }
802 838
803 - updateAlarms(alarms: AlarmInfo[]) {  
804 - alarms.forEach((newAlarm) => {  
805 - const existingAlarmIndex = this.allAlarms.findIndex(alarm => alarm.id.id === newAlarm.id.id);  
806 - if (existingAlarmIndex > -1) {  
807 - Object.assign(this.allAlarms[existingAlarmIndex], newAlarm);  
808 - } else {  
809 - this.allAlarms.push(newAlarm);  
810 - } 839 + updateAlarms() {
  840 + const subscriptionAlarms = this.subscription.alarms;
  841 + let alarms = new Array<AlarmDataInfo>();
  842 + subscriptionAlarms.data.forEach((alarmData) => {
  843 + alarms.push(this.alarmDataToInfo(alarmData));
811 }); 844 });
812 - for (let i = this.allAlarms.length - 1; i >= 0; i--) {  
813 - const oldAlarm = this.allAlarms[i];  
814 - const newAlarmIndex = alarms.findIndex(alarm => alarm.id.id === oldAlarm.id.id);  
815 - if (newAlarmIndex === -1) {  
816 - this.allAlarms.splice(i, 1);  
817 - } 845 + if (this.appliedSortOrderLabel && this.appliedSortOrderLabel.length) {
  846 + const asc = this.appliedPageLink.sortOrder.direction === Direction.ASC;
  847 + alarms = alarms.sort((a, b) => sortItems(a, b, this.appliedSortOrderLabel, asc));
818 } 848 }
819 if (this.selection.hasValue()) { 849 if (this.selection.hasValue()) {
820 - const toRemove: AlarmInfo[] = [];  
821 - this.selection.selected.forEach((selectedAlarm) => {  
822 - const existingAlarm = this.allAlarms.find(alarm => alarm.id.id === selectedAlarm.id.id);  
823 - if (!existingAlarm) {  
824 - toRemove.push(selectedAlarm);  
825 - }  
826 - }); 850 + const alarmIds = alarms.map((alarm) => alarm.id.id);
  851 + const toRemove = this.selection.selected.filter(alarmId => alarmIds.indexOf(alarmId) === -1);
827 this.selection.deselect(...toRemove); 852 this.selection.deselect(...toRemove);
828 if (this.selection.isEmpty()) { 853 if (this.selection.isEmpty()) {
829 this.onSelectionModeChanged(false); 854 this.onSelectionModeChanged(false);
830 } 855 }
831 } 856 }
832 - this.allAlarmsSubject.next(this.allAlarms); 857 + const alarmsPageData: PageData<AlarmDataInfo> = {
  858 + data: alarms,
  859 + totalPages: subscriptionAlarms.totalPages,
  860 + totalElements: subscriptionAlarms.totalElements,
  861 + hasNext: subscriptionAlarms.hasNext
  862 + };
  863 + this.alarmsSubject.next(alarms);
  864 + this.pageDataSubject.next(alarmsPageData);
  865 + this.dataLoading = false;
  866 + }
  867 +
  868 + private alarmDataToInfo(alarmData: AlarmData): AlarmDataInfo {
  869 + const alarm: AlarmDataInfo = deepClone(alarmData);
  870 + delete alarm.latest;
  871 + const latest = alarmData.latest;
  872 + this.dataKeys.forEach((dataKey, index) => {
  873 + const type = dataKeyTypeToEntityKeyType(dataKey.type);
  874 + let value = '';
  875 + if (type) {
  876 + if (latest && latest[type]) {
  877 + const tsVal = latest[type][dataKey.name];
  878 + if (tsVal) {
  879 + value = tsVal.value;
  880 + }
  881 + }
  882 + }
  883 + alarm[dataKey.label] = value;
  884 + });
  885 + return alarm;
833 } 886 }
834 887
835 isAllSelected(): Observable<boolean> { 888 isAllSelected(): Observable<boolean> {
@@ -851,16 +904,16 @@ class AlarmsDatasource implements DataSource<AlarmInfo> { @@ -851,16 +904,16 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
851 ); 904 );
852 } 905 }
853 906
854 - toggleSelection(alarm: AlarmInfo) { 907 + toggleSelection(alarm: AlarmDataInfo) {
855 const hasValue = this.selection.hasValue(); 908 const hasValue = this.selection.hasValue();
856 - this.selection.toggle(alarm); 909 + this.selection.toggle(alarm.id.id);
857 if (hasValue !== this.selection.hasValue()) { 910 if (hasValue !== this.selection.hasValue()) {
858 this.onSelectionModeChanged(this.selection.hasValue()); 911 this.onSelectionModeChanged(this.selection.hasValue());
859 } 912 }
860 } 913 }
861 914
862 - isSelected(alarm: AlarmInfo): boolean {  
863 - return this.selection.isSelected(alarm); 915 + isSelected(alarm: AlarmDataInfo): boolean {
  916 + return this.selection.isSelected(alarm.id.id);
864 } 917 }
865 918
866 clearSelection() { 919 clearSelection() {
@@ -881,7 +934,7 @@ class AlarmsDatasource implements DataSource<AlarmInfo> { @@ -881,7 +934,7 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
881 } 934 }
882 } else { 935 } else {
883 alarms.forEach(row => { 936 alarms.forEach(row => {
884 - this.selection.select(row); 937 + this.selection.select(row.id.id);
885 }); 938 });
886 if (numSelected === 0) { 939 if (numSelected === 0) {
887 this.onSelectionModeChanged(true); 940 this.onSelectionModeChanged(true);
@@ -892,7 +945,7 @@ class AlarmsDatasource implements DataSource<AlarmInfo> { @@ -892,7 +945,7 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
892 ).subscribe(); 945 ).subscribe();
893 } 946 }
894 947
895 - public toggleCurrentAlarm(alarm: AlarmInfo): boolean { 948 + public toggleCurrentAlarm(alarm: AlarmDataInfo): boolean {
896 if (this.currentAlarm !== alarm) { 949 if (this.currentAlarm !== alarm) {
897 this.currentAlarm = alarm; 950 this.currentAlarm = alarm;
898 return true; 951 return true;
@@ -901,7 +954,7 @@ class AlarmsDatasource implements DataSource<AlarmInfo> { @@ -901,7 +954,7 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
901 } 954 }
902 } 955 }
903 956
904 - public isCurrentAlarm(alarm: AlarmInfo): boolean { 957 + public isCurrentAlarm(alarm: AlarmDataInfo): boolean {
905 return (this.currentAlarm && alarm && this.currentAlarm.id && alarm.id) && 958 return (this.currentAlarm && alarm && this.currentAlarm.id && alarm.id) &&
906 (this.currentAlarm.id.id === alarm.id.id); 959 (this.currentAlarm.id.id === alarm.id.id);
907 } 960 }
@@ -909,10 +962,4 @@ class AlarmsDatasource implements DataSource<AlarmInfo> { @@ -909,10 +962,4 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
909 private onSelectionModeChanged(selectionMode: boolean) { 962 private onSelectionModeChanged(selectionMode: boolean) {
910 this.selectionModeChanged.emit(selectionMode); 963 this.selectionModeChanged.emit(selectionMode);
911 } 964 }
912 -  
913 - private fetchAlarms(pageLink: PageLink): Observable<PageData<AlarmInfo>> {  
914 - return this.allAlarms$.pipe(  
915 - map((data) => pageLink.filterData(data))  
916 - );  
917 - }  
918 } 965 }
@@ -77,6 +77,7 @@ import { @@ -77,6 +77,7 @@ import {
77 DisplayColumnsPanelData 77 DisplayColumnsPanelData
78 } from '@home/components/widget/lib/display-columns-panel.component'; 78 } from '@home/components/widget/lib/display-columns-panel.component';
79 import { 79 import {
  80 + dataKeyToEntityKey,
80 Direction, 81 Direction,
81 EntityDataPageLink, 82 EntityDataPageLink,
82 entityDataPageLinkSortDirection, 83 entityDataPageLinkSortDirection,
@@ -205,7 +206,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni @@ -205,7 +206,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
205 206
206 public onDataUpdated() { 207 public onDataUpdated() {
207 this.ngZone.run(() => { 208 this.ngZone.run(() => {
208 - this.entityDatasource.dataUpdated(); // .updateEntitiesData(this.subscription.data); 209 + this.entityDatasource.dataUpdated();
209 this.ctx.detectChanges(); 210 this.ctx.detectChanges();
210 }); 211 });
211 } 212 }
@@ -339,19 +340,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni @@ -339,19 +340,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
339 if (datasource && datasource.dataKeys) { 340 if (datasource && datasource.dataKeys) {
340 datasource.dataKeys.forEach((entityDataKey) => { 341 datasource.dataKeys.forEach((entityDataKey) => {
341 const dataKey: EntityColumn = deepClone(entityDataKey) as EntityColumn; 342 const dataKey: EntityColumn = deepClone(entityDataKey) as EntityColumn;
342 - dataKey.entityKey = {  
343 - key: dataKey.name,  
344 - type: null  
345 - }; 343 + dataKey.entityKey = dataKeyToEntityKey(entityDataKey);
346 if (dataKey.type === DataKeyType.function) { 344 if (dataKey.type === DataKeyType.function) {
347 dataKey.name = dataKey.label; 345 dataKey.name = dataKey.label;
348 - dataKey.entityKey.type = EntityKeyType.ENTITY_FIELD;  
349 - } else if (dataKey.type === DataKeyType.entityField) {  
350 - dataKey.entityKey.type = EntityKeyType.ENTITY_FIELD;  
351 - } else if (dataKey.type === DataKeyType.attribute) {  
352 - dataKey.entityKey.type = EntityKeyType.ATTRIBUTE;  
353 - } else if (dataKey.type === DataKeyType.timeseries) {  
354 - dataKey.entityKey.type = EntityKeyType.TIME_SERIES;  
355 } 346 }
356 dataKeys.push(dataKey); 347 dataKeys.push(dataKey);
357 348
@@ -17,9 +17,10 @@ @@ -17,9 +17,10 @@
17 import { EntityId } from '@shared/models/id/entity-id'; 17 import { EntityId } from '@shared/models/id/entity-id';
18 import { DataKey, WidgetConfig } from '@shared/models/widget.models'; 18 import { DataKey, WidgetConfig } from '@shared/models/widget.models';
19 import { getDescendantProp, isDefined } from '@core/utils'; 19 import { getDescendantProp, isDefined } from '@core/utils';
20 -import { alarmFields, AlarmInfo } from '@shared/models/alarm.models'; 20 +import { AlarmDataInfo, alarmFields } from '@shared/models/alarm.models';
21 import * as tinycolor_ from 'tinycolor2'; 21 import * as tinycolor_ from 'tinycolor2';
22 import { Direction, EntityDataSortOrder, EntityKey } from '@shared/models/query/query.models'; 22 import { Direction, EntityDataSortOrder, EntityKey } from '@shared/models/query/query.models';
  23 +import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
23 24
24 const tinycolor = tinycolor_; 25 const tinycolor = tinycolor_;
25 26
@@ -120,7 +121,15 @@ export function findColumn(searchProperty: string, searchValue: string, columns: @@ -120,7 +121,15 @@ export function findColumn(searchProperty: string, searchValue: string, columns:
120 } 121 }
121 122
122 export function findColumnByLabel(label: string, columns: EntityColumn[]): EntityColumn { 123 export function findColumnByLabel(label: string, columns: EntityColumn[]): EntityColumn {
123 - return findColumn('label', label, columns); 124 + let column: EntityColumn;
  125 + const alarmColumns = columns.filter(c => c.type === DataKeyType.alarm);
  126 + if (alarmColumns.length) {
  127 + column = findColumn('name', label, alarmColumns);
  128 + }
  129 + if (!column) {
  130 + column = findColumn('label', label, columns);
  131 + }
  132 + return column;
124 } 133 }
125 134
126 export function findColumnByDef(def: string, columns: EntityColumn[]): EntityColumn { 135 export function findColumnByDef(def: string, columns: EntityColumn[]): EntityColumn {
@@ -160,12 +169,12 @@ export function getEntityValue(entity: any, key: DataKey): any { @@ -160,12 +169,12 @@ export function getEntityValue(entity: any, key: DataKey): any {
160 return getDescendantProp(entity, key.label); 169 return getDescendantProp(entity, key.label);
161 } 170 }
162 171
163 -export function getAlarmValue(alarm: AlarmInfo, key: EntityColumn) { 172 +export function getAlarmValue(alarm: AlarmDataInfo, key: EntityColumn) {
164 const alarmField = alarmFields[key.name]; 173 const alarmField = alarmFields[key.name];
165 if (alarmField) { 174 if (alarmField) {
166 return getDescendantProp(alarm, alarmField.value); 175 return getDescendantProp(alarm, alarmField.value);
167 } else { 176 } else {
168 - return getDescendantProp(alarm, key.name); 177 + return getDescendantProp(alarm, key.label);
169 } 178 }
170 } 179 }
171 180
@@ -94,6 +94,7 @@ import { ResizeObserver } from '@juggle/resize-observer'; @@ -94,6 +94,7 @@ import { ResizeObserver } from '@juggle/resize-observer';
94 import { EntityDataService } from '@core/api/entity-data.service'; 94 import { EntityDataService } from '@core/api/entity-data.service';
95 import { TranslateService } from '@ngx-translate/core'; 95 import { TranslateService } from '@ngx-translate/core';
96 import { NotificationType } from '@core/notification/notification.models'; 96 import { NotificationType } from '@core/notification/notification.models';
  97 +import { AlarmDataService } from '@core/api/alarm-data.service';
97 98
98 @Component({ 99 @Component({
99 selector: 'tb-widget', 100 selector: 'tb-widget',
@@ -167,9 +168,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @@ -167,9 +168,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
167 private timeService: TimeService, 168 private timeService: TimeService,
168 private deviceService: DeviceService, 169 private deviceService: DeviceService,
169 private entityService: EntityService, 170 private entityService: EntityService,
170 - private alarmService: AlarmService,  
171 private dashboardService: DashboardService, 171 private dashboardService: DashboardService,
172 private entityDataService: EntityDataService, 172 private entityDataService: EntityDataService,
  173 + private alarmDataService: AlarmDataService,
173 private translate: TranslateService, 174 private translate: TranslateService,
174 private utils: UtilsService, 175 private utils: UtilsService,
175 private raf: RafService, 176 private raf: RafService,
@@ -300,9 +301,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @@ -300,9 +301,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
300 this.subscriptionContext = new WidgetSubscriptionContext(this.widgetContext.dashboard); 301 this.subscriptionContext = new WidgetSubscriptionContext(this.widgetContext.dashboard);
301 this.subscriptionContext.timeService = this.timeService; 302 this.subscriptionContext.timeService = this.timeService;
302 this.subscriptionContext.deviceService = this.deviceService; 303 this.subscriptionContext.deviceService = this.deviceService;
303 - this.subscriptionContext.alarmService = this.alarmService;  
304 this.subscriptionContext.translate = this.translate; 304 this.subscriptionContext.translate = this.translate;
305 this.subscriptionContext.entityDataService = this.entityDataService; 305 this.subscriptionContext.entityDataService = this.entityDataService;
  306 + this.subscriptionContext.alarmDataService = this.alarmDataService;
306 this.subscriptionContext.utils = this.utils; 307 this.subscriptionContext.utils = this.utils;
307 this.subscriptionContext.raf = this.raf; 308 this.subscriptionContext.raf = this.raf;
308 this.subscriptionContext.widgetUtils = this.widgetContext.utils; 309 this.subscriptionContext.widgetUtils = this.widgetContext.utils;
@@ -901,14 +902,14 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI @@ -901,14 +902,14 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
901 }; 902 };
902 if (this.widget.type === widgetType.alarm) { 903 if (this.widget.type === widgetType.alarm) {
903 options.alarmSource = deepClone(this.widget.config.alarmSource); 904 options.alarmSource = deepClone(this.widget.config.alarmSource);
904 - options.alarmSearchStatus = isDefined(this.widget.config.alarmSearchStatus) ? 905 + /*options.alarmSearchStatus = isDefined(this.widget.config.alarmSearchStatus) ?
905 this.widget.config.alarmSearchStatus : AlarmSearchStatus.ANY; 906 this.widget.config.alarmSearchStatus : AlarmSearchStatus.ANY;
906 options.alarmsPollingInterval = isDefined(this.widget.config.alarmsPollingInterval) ? 907 options.alarmsPollingInterval = isDefined(this.widget.config.alarmsPollingInterval) ?
907 this.widget.config.alarmsPollingInterval * 1000 : 5000; 908 this.widget.config.alarmsPollingInterval * 1000 : 5000;
908 options.alarmsMaxCountLoad = isDefined(this.widget.config.alarmsMaxCountLoad) ? 909 options.alarmsMaxCountLoad = isDefined(this.widget.config.alarmsMaxCountLoad) ?
909 this.widget.config.alarmsMaxCountLoad : 0; 910 this.widget.config.alarmsMaxCountLoad : 0;
910 options.alarmsFetchSize = isDefined(this.widget.config.alarmsFetchSize) ? 911 options.alarmsFetchSize = isDefined(this.widget.config.alarmsFetchSize) ?
911 - this.widget.config.alarmsFetchSize : 100; 912 + this.widget.config.alarmsFetchSize : 100;*/
912 } else { 913 } else {
913 options.datasources = deepClone(this.widget.config.datasources); 914 options.datasources = deepClone(this.widget.config.datasources);
914 } 915 }
@@ -103,6 +103,10 @@ export interface AlarmInfo extends Alarm { @@ -103,6 +103,10 @@ export interface AlarmInfo extends Alarm {
103 originatorName: string; 103 originatorName: string;
104 } 104 }
105 105
  106 +export interface AlarmDataInfo extends AlarmInfo {
  107 + [key: string]: any;
  108 +}
  109 +
106 export const simulatedAlarm: AlarmInfo = { 110 export const simulatedAlarm: AlarmInfo = {
107 id: new AlarmId(NULL_UUID), 111 id: new AlarmId(NULL_UUID),
108 tenantId: new TenantId(NULL_UUID), 112 tenantId: new TenantId(NULL_UUID),
@@ -20,10 +20,11 @@ import { SortDirection } from '@angular/material/sort'; @@ -20,10 +20,11 @@ import { SortDirection } from '@angular/material/sort';
20 import { DataKeyType } from '@shared/models/telemetry/telemetry.models'; 20 import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
21 import { EntityInfo } from '@shared/models/entity.models'; 21 import { EntityInfo } from '@shared/models/entity.models';
22 import { EntityType } from '@shared/models/entity-type.models'; 22 import { EntityType } from '@shared/models/entity-type.models';
23 -import { Datasource, DatasourceType } from '@shared/models/widget.models'; 23 +import { DataKey, Datasource, DatasourceType } from '@shared/models/widget.models';
24 import { PageData } from '@shared/models/page/page-data'; 24 import { PageData } from '@shared/models/page/page-data';
25 import { isDefined, isEqual } from '@core/utils'; 25 import { isDefined, isEqual } from '@core/utils';
26 import { TranslateService } from '@ngx-translate/core'; 26 import { TranslateService } from '@ngx-translate/core';
  27 +import { AlarmInfo, AlarmSearchStatus, AlarmSeverity } from '../alarm.models';
27 28
28 export enum EntityKeyType { 29 export enum EntityKeyType {
29 ATTRIBUTE = 'ATTRIBUTE', 30 ATTRIBUTE = 'ATTRIBUTE',
@@ -31,7 +32,8 @@ export enum EntityKeyType { @@ -31,7 +32,8 @@ export enum EntityKeyType {
31 SHARED_ATTRIBUTE = 'SHARED_ATTRIBUTE', 32 SHARED_ATTRIBUTE = 'SHARED_ATTRIBUTE',
32 SERVER_ATTRIBUTE = 'SERVER_ATTRIBUTE', 33 SERVER_ATTRIBUTE = 'SERVER_ATTRIBUTE',
33 TIME_SERIES = 'TIME_SERIES', 34 TIME_SERIES = 'TIME_SERIES',
34 - ENTITY_FIELD = 'ENTITY_FIELD' 35 + ENTITY_FIELD = 'ENTITY_FIELD',
  36 + ALARM_FIELD = 'ENTITY_FIELD'
35 } 37 }
36 38
37 export const entityKeyTypeTranslationMap = new Map<EntityKeyType, string>( 39 export const entityKeyTypeTranslationMap = new Map<EntityKeyType, string>(
@@ -53,6 +55,23 @@ export function entityKeyTypeToDataKeyType(entityKeyType: EntityKeyType): DataKe @@ -53,6 +55,23 @@ export function entityKeyTypeToDataKeyType(entityKeyType: EntityKeyType): DataKe
53 return DataKeyType.timeseries; 55 return DataKeyType.timeseries;
54 case EntityKeyType.ENTITY_FIELD: 56 case EntityKeyType.ENTITY_FIELD:
55 return DataKeyType.entityField; 57 return DataKeyType.entityField;
  58 + case EntityKeyType.ALARM_FIELD:
  59 + return DataKeyType.alarm;
  60 + }
  61 +}
  62 +
  63 +export function dataKeyTypeToEntityKeyType(dataKeyType: DataKeyType): EntityKeyType {
  64 + switch (dataKeyType) {
  65 + case DataKeyType.timeseries:
  66 + return EntityKeyType.TIME_SERIES;
  67 + case DataKeyType.attribute:
  68 + return EntityKeyType.ATTRIBUTE;
  69 + case DataKeyType.function:
  70 + return EntityKeyType.ENTITY_FIELD;
  71 + case DataKeyType.alarm:
  72 + return EntityKeyType.ALARM_FIELD;
  73 + case DataKeyType.entityField:
  74 + return EntityKeyType.ENTITY_FIELD;
56 } 75 }
57 } 76 }
58 77
@@ -61,6 +80,14 @@ export interface EntityKey { @@ -61,6 +80,14 @@ export interface EntityKey {
61 key: string; 80 key: string;
62 } 81 }
63 82
  83 +export function dataKeyToEntityKey(dataKey: DataKey): EntityKey {
  84 + const entityKey: EntityKey = {
  85 + key: dataKey.name,
  86 + type: dataKeyTypeToEntityKeyType(dataKey.type)
  87 + };
  88 + return entityKey;
  89 +}
  90 +
64 export enum EntityKeyValueType { 91 export enum EntityKeyValueType {
65 STRING = 'STRING', 92 STRING = 'STRING',
66 NUMERIC = 'NUMERIC', 93 NUMERIC = 'NUMERIC',
@@ -479,6 +506,16 @@ export interface EntityDataPageLink { @@ -479,6 +506,16 @@ export interface EntityDataPageLink {
479 dynamic?: boolean; 506 dynamic?: boolean;
480 } 507 }
481 508
  509 +export interface AlarmDataPageLink extends EntityDataPageLink {
  510 + startTs?: number;
  511 + endTs?: number;
  512 + timeWindow?: number;
  513 + typeList?: Array<string>;
  514 + statusList?: Array<AlarmSearchStatus>;
  515 + severityList?: Array<AlarmSeverity>;
  516 + searchPropagatedAlarms?: boolean;
  517 +}
  518 +
482 export function entityDataPageLinkSortDirection(pageLink: EntityDataPageLink): SortDirection { 519 export function entityDataPageLinkSortDirection(pageLink: EntityDataPageLink): SortDirection {
483 if (pageLink.sortOrder) { 520 if (pageLink.sortOrder) {
484 return (pageLink.sortOrder.direction + '').toLowerCase() as SortDirection; 521 return (pageLink.sortOrder.direction + '').toLowerCase() as SortDirection;
@@ -508,13 +545,19 @@ export interface EntityCountQuery { @@ -508,13 +545,19 @@ export interface EntityCountQuery {
508 entityFilter: EntityFilter; 545 entityFilter: EntityFilter;
509 } 546 }
510 547
511 -export interface EntityDataQuery extends EntityCountQuery {  
512 - pageLink: EntityDataPageLink; 548 +export interface AbstractDataQuery<T extends EntityDataPageLink> extends EntityCountQuery {
  549 + pageLink: T;
513 entityFields?: Array<EntityKey>; 550 entityFields?: Array<EntityKey>;
514 latestValues?: Array<EntityKey>; 551 latestValues?: Array<EntityKey>;
515 keyFilters?: Array<KeyFilter>; 552 keyFilters?: Array<KeyFilter>;
516 } 553 }
517 554
  555 +export interface EntityDataQuery extends AbstractDataQuery<EntityDataPageLink> {
  556 +}
  557 +
  558 +export interface AlarmDataQuery extends AbstractDataQuery<AlarmDataPageLink> {
  559 +}
  560 +
518 export interface TsValue { 561 export interface TsValue {
519 ts: number; 562 ts: number;
520 value: string; 563 value: string;
@@ -526,6 +569,11 @@ export interface EntityData { @@ -526,6 +569,11 @@ export interface EntityData {
526 timeseries: {[key: string]: Array<TsValue>}; 569 timeseries: {[key: string]: Array<TsValue>};
527 } 570 }
528 571
  572 +export interface AlarmData extends AlarmInfo {
  573 + entityId: string;
  574 + latest: {[entityKeyType: string]: {[key: string]: TsValue}};
  575 +}
  576 +
529 export function entityPageDataChanged(prevPageData: PageData<EntityData>, nextPageData: PageData<EntityData>): boolean { 577 export function entityPageDataChanged(prevPageData: PageData<EntityData>, nextPageData: PageData<EntityData>): boolean {
530 const prevIds = prevPageData.data.map((entityData) => entityData.entityId.id); 578 const prevIds = prevPageData.data.map((entityData) => entityData.entityId.id);
531 const nextIds = nextPageData.data.map((entityData) => entityData.entityId.id); 579 const nextIds = nextPageData.data.map((entityData) => entityData.entityId.id);
@@ -21,7 +21,7 @@ import { Observable, ReplaySubject, Subject } from 'rxjs'; @@ -21,7 +21,7 @@ import { Observable, ReplaySubject, Subject } from 'rxjs';
21 import { EntityId } from '@shared/models/id/entity-id'; 21 import { EntityId } from '@shared/models/id/entity-id';
22 import { map } from 'rxjs/operators'; 22 import { map } from 'rxjs/operators';
23 import { NgZone } from '@angular/core'; 23 import { NgZone } from '@angular/core';
24 -import { EntityData, EntityDataQuery, EntityKey } from '@shared/models/query/query.models'; 24 +import { AlarmData, AlarmDataQuery, EntityData, EntityDataQuery, EntityKey } from '@shared/models/query/query.models';
25 import { PageData } from '@shared/models/page/page-data'; 25 import { PageData } from '@shared/models/page/page-data';
26 26
27 export enum DataKeyType { 27 export enum DataKeyType {
@@ -165,16 +165,31 @@ export class EntityDataCmd implements WebsocketCmd { @@ -165,16 +165,31 @@ export class EntityDataCmd implements WebsocketCmd {
165 } 165 }
166 } 166 }
167 167
  168 +export class AlarmDataCmd implements WebsocketCmd {
  169 + cmdId: number;
  170 + query?: AlarmDataQuery;
  171 +
  172 + public isEmpty(): boolean {
  173 + return !this.query;
  174 + }
  175 +}
  176 +
168 export class EntityDataUnsubscribeCmd implements WebsocketCmd { 177 export class EntityDataUnsubscribeCmd implements WebsocketCmd {
169 cmdId: number; 178 cmdId: number;
170 } 179 }
171 180
  181 +export class AlarmDataUnsubscribeCmd implements WebsocketCmd {
  182 + cmdId: number;
  183 +}
  184 +
172 export class TelemetryPluginCmdsWrapper { 185 export class TelemetryPluginCmdsWrapper {
173 attrSubCmds: Array<AttributesSubscriptionCmd>; 186 attrSubCmds: Array<AttributesSubscriptionCmd>;
174 tsSubCmds: Array<TimeseriesSubscriptionCmd>; 187 tsSubCmds: Array<TimeseriesSubscriptionCmd>;
175 historyCmds: Array<GetHistoryCmd>; 188 historyCmds: Array<GetHistoryCmd>;
176 entityDataCmds: Array<EntityDataCmd>; 189 entityDataCmds: Array<EntityDataCmd>;
177 entityDataUnsubscribeCmds: Array<EntityDataUnsubscribeCmd>; 190 entityDataUnsubscribeCmds: Array<EntityDataUnsubscribeCmd>;
  191 + alarmDataCmds: Array<AlarmDataCmd>;
  192 + alarmDataUnsubscribeCmds: Array<AlarmDataUnsubscribeCmd>;
178 193
179 constructor() { 194 constructor() {
180 this.attrSubCmds = []; 195 this.attrSubCmds = [];
@@ -182,6 +197,8 @@ export class TelemetryPluginCmdsWrapper { @@ -182,6 +197,8 @@ export class TelemetryPluginCmdsWrapper {
182 this.historyCmds = []; 197 this.historyCmds = [];
183 this.entityDataCmds = []; 198 this.entityDataCmds = [];
184 this.entityDataUnsubscribeCmds = []; 199 this.entityDataUnsubscribeCmds = [];
  200 + this.alarmDataCmds = [];
  201 + this.alarmDataUnsubscribeCmds = [];
185 } 202 }
186 203
187 public hasCommands(): boolean { 204 public hasCommands(): boolean {
@@ -189,7 +206,9 @@ export class TelemetryPluginCmdsWrapper { @@ -189,7 +206,9 @@ export class TelemetryPluginCmdsWrapper {
189 this.historyCmds.length > 0 || 206 this.historyCmds.length > 0 ||
190 this.attrSubCmds.length > 0 || 207 this.attrSubCmds.length > 0 ||
191 this.entityDataCmds.length > 0 || 208 this.entityDataCmds.length > 0 ||
192 - this.entityDataUnsubscribeCmds.length > 0; 209 + this.entityDataUnsubscribeCmds.length > 0 ||
  210 + this.alarmDataCmds.length > 0 ||
  211 + this.alarmDataUnsubscribeCmds.length > 0;
193 } 212 }
194 213
195 public clear() { 214 public clear() {
@@ -198,6 +217,8 @@ export class TelemetryPluginCmdsWrapper { @@ -198,6 +217,8 @@ export class TelemetryPluginCmdsWrapper {
198 this.historyCmds.length = 0; 217 this.historyCmds.length = 0;
199 this.entityDataCmds.length = 0; 218 this.entityDataCmds.length = 0;
200 this.entityDataUnsubscribeCmds.length = 0; 219 this.entityDataUnsubscribeCmds.length = 0;
  220 + this.alarmDataCmds.length = 0;
  221 + this.alarmDataUnsubscribeCmds.length = 0;
201 } 222 }
202 223
203 public preparePublishCommands(maxCommands: number): TelemetryPluginCmdsWrapper { 224 public preparePublishCommands(maxCommands: number): TelemetryPluginCmdsWrapper {
@@ -212,6 +233,10 @@ export class TelemetryPluginCmdsWrapper { @@ -212,6 +233,10 @@ export class TelemetryPluginCmdsWrapper {
212 preparedWrapper.entityDataCmds = this.popCmds(this.entityDataCmds, leftCount); 233 preparedWrapper.entityDataCmds = this.popCmds(this.entityDataCmds, leftCount);
213 leftCount -= preparedWrapper.entityDataCmds.length; 234 leftCount -= preparedWrapper.entityDataCmds.length;
214 preparedWrapper.entityDataUnsubscribeCmds = this.popCmds(this.entityDataUnsubscribeCmds, leftCount); 235 preparedWrapper.entityDataUnsubscribeCmds = this.popCmds(this.entityDataUnsubscribeCmds, leftCount);
  236 + leftCount -= preparedWrapper.entityDataUnsubscribeCmds.length;
  237 + preparedWrapper.alarmDataCmds = this.popCmds(this.alarmDataCmds, leftCount);
  238 + leftCount -= preparedWrapper.alarmDataCmds.length;
  239 + preparedWrapper.alarmDataUnsubscribeCmds = this.popCmds(this.alarmDataUnsubscribeCmds, leftCount);
215 return preparedWrapper; 240 return preparedWrapper;
216 } 241 }
217 242
@@ -239,18 +264,38 @@ export interface SubscriptionUpdateMsg extends SubscriptionDataHolder { @@ -239,18 +264,38 @@ export interface SubscriptionUpdateMsg extends SubscriptionDataHolder {
239 errorMsg: string; 264 errorMsg: string;
240 } 265 }
241 266
242 -export interface EntityDataUpdateMsg { 267 +export enum DataUpdateType {
  268 + ENTITY_DATA = 'ENTITY_DATA',
  269 + ALARM_DATA = 'ALARM_DATA'
  270 +}
  271 +
  272 +export interface DataUpdateMsg<T> {
243 cmdId: number; 273 cmdId: number;
244 - data?: PageData<EntityData>;  
245 - update?: Array<EntityData>; 274 + data?: PageData<T>;
  275 + update?: Array<T>;
246 errorCode: number; 276 errorCode: number;
247 errorMsg: string; 277 errorMsg: string;
  278 + dataUpdateType: DataUpdateType;
  279 +}
  280 +
  281 +export interface EntityDataUpdateMsg extends DataUpdateMsg<EntityData> {
  282 + dataUpdateType: DataUpdateType.ENTITY_DATA;
248 } 283 }
249 284
250 -export type WebsocketDataMsg = EntityDataUpdateMsg | SubscriptionUpdateMsg; 285 +export interface AlarmDataUpdateMsg extends DataUpdateMsg<AlarmData> {
  286 + dataUpdateType: DataUpdateType.ALARM_DATA;
  287 +}
  288 +
  289 +export type WebsocketDataMsg = AlarmDataUpdateMsg | EntityDataUpdateMsg | SubscriptionUpdateMsg;
251 290
252 export function isEntityDataUpdateMsg(message: WebsocketDataMsg): message is EntityDataUpdateMsg { 291 export function isEntityDataUpdateMsg(message: WebsocketDataMsg): message is EntityDataUpdateMsg {
253 - return (message as EntityDataUpdateMsg).cmdId !== undefined; 292 + const updateMsg = (message as DataUpdateMsg<any>);
  293 + return updateMsg.cmdId !== undefined && updateMsg.dataUpdateType === DataUpdateType.ENTITY_DATA;
  294 +}
  295 +
  296 +export function isAlarmDataUpdateMsg(message: WebsocketDataMsg): message is AlarmDataUpdateMsg {
  297 + const updateMsg = (message as DataUpdateMsg<any>);
  298 + return updateMsg.cmdId !== undefined && updateMsg.dataUpdateType === DataUpdateType.ALARM_DATA;
254 } 299 }
255 300
256 export class SubscriptionUpdate implements SubscriptionUpdateMsg { 301 export class SubscriptionUpdate implements SubscriptionUpdateMsg {
@@ -302,19 +347,33 @@ export class SubscriptionUpdate implements SubscriptionUpdateMsg { @@ -302,19 +347,33 @@ export class SubscriptionUpdate implements SubscriptionUpdateMsg {
302 } 347 }
303 } 348 }
304 349
305 -export class EntityDataUpdate implements EntityDataUpdateMsg { 350 +export class DataUpdate<T> implements DataUpdateMsg<T> {
306 cmdId: number; 351 cmdId: number;
307 errorCode: number; 352 errorCode: number;
308 errorMsg: string; 353 errorMsg: string;
309 - data?: PageData<EntityData>;  
310 - update?: Array<EntityData>; 354 + data?: PageData<T>;
  355 + update?: Array<T>;
  356 + dataUpdateType: DataUpdateType;
311 357
312 - constructor(msg: EntityDataUpdateMsg) { 358 + constructor(msg: DataUpdateMsg<T>) {
313 this.cmdId = msg.cmdId; 359 this.cmdId = msg.cmdId;
314 this.errorCode = msg.errorCode; 360 this.errorCode = msg.errorCode;
315 this.errorMsg = msg.errorMsg; 361 this.errorMsg = msg.errorMsg;
316 this.data = msg.data; 362 this.data = msg.data;
317 this.update = msg.update; 363 this.update = msg.update;
  364 + this.dataUpdateType = msg.dataUpdateType;
  365 + }
  366 +}
  367 +
  368 +export class EntityDataUpdate extends DataUpdate<EntityData> {
  369 + constructor(msg: EntityDataUpdateMsg) {
  370 + super(msg);
  371 + }
  372 +}
  373 +
  374 +export class AlarmDataUpdate extends DataUpdate<AlarmData> {
  375 + constructor(msg: AlarmDataUpdateMsg) {
  376 + super(msg);
318 } 377 }
319 } 378 }
320 379
@@ -328,6 +387,7 @@ export class TelemetrySubscriber { @@ -328,6 +387,7 @@ export class TelemetrySubscriber {
328 387
329 private dataSubject = new ReplaySubject<SubscriptionUpdate>(1); 388 private dataSubject = new ReplaySubject<SubscriptionUpdate>(1);
330 private entityDataSubject = new ReplaySubject<EntityDataUpdate>(1); 389 private entityDataSubject = new ReplaySubject<EntityDataUpdate>(1);
  390 + private alarmDataSubject = new ReplaySubject<AlarmDataUpdate>(1);
331 private reconnectSubject = new Subject(); 391 private reconnectSubject = new Subject();
332 392
333 private zone: NgZone; 393 private zone: NgZone;
@@ -336,6 +396,7 @@ export class TelemetrySubscriber { @@ -336,6 +396,7 @@ export class TelemetrySubscriber {
336 396
337 public data$ = this.dataSubject.asObservable(); 397 public data$ = this.dataSubject.asObservable();
338 public entityData$ = this.entityDataSubject.asObservable(); 398 public entityData$ = this.entityDataSubject.asObservable();
  399 + public alarmData$ = this.alarmDataSubject.asObservable();
339 public reconnect$ = this.reconnectSubject.asObservable(); 400 public reconnect$ = this.reconnectSubject.asObservable();
340 401
341 public static createEntityAttributesSubscription(telemetryService: TelemetryService, 402 public static createEntityAttributesSubscription(telemetryService: TelemetryService,
@@ -379,6 +440,7 @@ export class TelemetrySubscriber { @@ -379,6 +440,7 @@ export class TelemetrySubscriber {
379 public complete() { 440 public complete() {
380 this.dataSubject.complete(); 441 this.dataSubject.complete();
381 this.entityDataSubject.complete(); 442 this.entityDataSubject.complete();
  443 + this.alarmDataSubject.complete();
382 this.reconnectSubject.complete(); 444 this.reconnectSubject.complete();
383 } 445 }
384 446
@@ -416,6 +478,18 @@ export class TelemetrySubscriber { @@ -416,6 +478,18 @@ export class TelemetrySubscriber {
416 } 478 }
417 } 479 }
418 480
  481 + public onAlarmData(message: AlarmDataUpdate) {
  482 + if (this.zone) {
  483 + this.zone.run(
  484 + () => {
  485 + this.alarmDataSubject.next(message);
  486 + }
  487 + );
  488 + } else {
  489 + this.alarmDataSubject.next(message);
  490 + }
  491 + }
  492 +
419 public onReconnected() { 493 public onReconnected() {
420 this.reconnectSubject.next(); 494 this.reconnectSubject.next();
421 } 495 }