Commit 4747e110bc29eabdc43fe54f1aebe844f643811e

Authored by Igor Kulikov
1 parent 7a19dee3

UI: Alarm data query support

... ... @@ -256,7 +256,7 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc
256 256 } else {
257 257 entitiesSortOrder = sortOrder;
258 258 }
259   - EntityDataPageLink edpl = new EntityDataPageLink(0, maxEntitiesPerAlarmSubscription, null, entitiesSortOrder);
  259 + EntityDataPageLink edpl = new EntityDataPageLink(maxEntitiesPerAlarmSubscription, 0, null, entitiesSortOrder);
260 260 EntityDataQuery edq = new EntityDataQuery(adq.getEntityFilter(), edpl, adq.getEntityFields(), adq.getLatestValues(), adq.getKeyFilters());
261 261 PageData<EntityData> entitiesData = entityService.findEntityDataByQuery(ctx.getTenantId(), ctx.getCustomerId(), edq);
262 262 List<EntityData> entities = entitiesData.getData();
... ...
... ... @@ -35,6 +35,11 @@ public class AlarmDataUpdate extends DataUpdate<AlarmData> {
35 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 43 @JsonCreator
39 44 public AlarmDataUpdate(@JsonProperty("cmdId") int cmdId,
40 45 @JsonProperty("data") PageData<AlarmData> data,
... ...
... ... @@ -40,4 +40,5 @@ public abstract class DataUpdate<T> {
40 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 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 41 @JsonCreator
37 42 public EntityDataUpdate(@JsonProperty("cmdId") int cmdId,
38 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 41 import { IDashboardComponent } from '@home/models/dashboard-component.models';
42 42 import * as moment_ from 'moment';
43 43 import {
  44 + AlarmData, AlarmDataPageLink,
44 45 EntityData,
45 46 EntityDataPageLink,
46 47 EntityFilter,
... ... @@ -51,6 +52,7 @@ import {
51 52 import { EntityDataService } from '@core/api/entity-data.service';
52 53 import { PageData } from '@shared/models/page/page-data';
53 54 import { TranslateService } from '@ngx-translate/core';
  55 +import { AlarmDataService } from '@core/api/alarm-data.service';
54 56
55 57 export interface TimewindowFunctions {
56 58 onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void;
... ... @@ -184,9 +186,9 @@ export class WidgetSubscriptionContext {
184 186
185 187 timeService: TimeService;
186 188 deviceService: DeviceService;
187   - alarmService: AlarmService;
188 189 translate: TranslateService;
189 190 entityDataService: EntityDataService;
  191 + alarmDataService: AlarmDataService;
190 192 utils: UtilsService;
191 193 raf: RafService;
192 194 widgetUtils: IWidgetUtils;
... ... @@ -218,10 +220,10 @@ export interface WidgetSubscriptionOptions {
218 220 type?: widgetType;
219 221 stateData?: boolean;
220 222 alarmSource?: Datasource;
221   - alarmSearchStatus?: AlarmSearchStatus;
  223 +/* alarmSearchStatus?: AlarmSearchStatus;
222 224 alarmsPollingInterval?: number;
223 225 alarmsMaxCountLoad?: number;
224   - alarmsFetchSize?: number;
  226 + alarmsFetchSize?: number; */
225 227 datasources?: Array<Datasource>;
226 228 hasDataPageLink?: boolean;
227 229 singleEntity?: boolean;
... ... @@ -269,10 +271,10 @@ export interface IWidgetSubscription {
269 271 timeWindow?: WidgetTimewindow;
270 272 comparisonTimeWindow?: WidgetTimewindow;
271 273
272   - alarms?: Array<AlarmInfo>;
  274 + alarms?: PageData<AlarmData>;
273 275 alarmSource?: Datasource;
274   - alarmSearchStatus?: AlarmSearchStatus;
275   - alarmsPollingInterval?: number;
  276 + /* alarmSearchStatus?: AlarmSearchStatus;
  277 + alarmsPollingInterval?: number; */
276 278
277 279 targetDeviceAliasIds?: Array<string>;
278 280 targetDeviceIds?: Array<string>;
... ... @@ -309,6 +311,9 @@ export interface IWidgetSubscription {
309 311 pageLink: EntityDataPageLink,
310 312 keyFilters: KeyFilter[]): Observable<any>;
311 313
  314 + subscribeForAlarms(pageLink: AlarmDataPageLink,
  315 + keyFilters: KeyFilter[]): void;
  316 +
312 317 isDataResolved(): boolean;
313 318
314 319 destroy(): void;
... ...
... ... @@ -47,21 +47,23 @@ import {
47 47 import { forkJoin, Observable, of, ReplaySubject, Subject, throwError } from 'rxjs';
48 48 import { CancelAnimationFrame } from '@core/services/raf.service';
49 49 import { EntityType } from '@shared/models/entity-type.models';
50   -import { AlarmInfo, AlarmSearchStatus } from '@shared/models/alarm.models';
51 50 import { createLabelFromDatasource, deepClone, isDefined, isEqual } from '@core/utils';
52   -import { AlarmSourceListener } from '@core/http/alarm.service';
53 51 import { EntityId } from '@app/shared/models/id/entity-id';
54 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 54 import { EntityDataListener } from '@core/api/entity-data.service';
57 55 import {
  56 + AlarmData,
  57 + AlarmDataPageLink,
58 58 EntityData,
59 59 EntityDataPageLink,
60 60 entityDataToEntityInfo,
  61 + EntityKeyType,
61 62 KeyFilter,
62 63 updateDatasourceFromEntityInfo
63 64 } from '@shared/models/query/query.models';
64 65 import { map } from 'rxjs/operators';
  66 +import { AlarmDataListener } from '@core/api/alarm-data.service';
65 67
66 68 const moment = moment_;
67 69
... ... @@ -102,10 +104,11 @@ export class WidgetSubscription implements IWidgetSubscription {
102 104 comparisonTimeWindow: WidgetTimewindow;
103 105 timewindowForComparison: SubscriptionTimewindow;
104 106
105   - alarms: Array<AlarmInfo>;
  107 + // alarms: Array<AlarmInfo>;
  108 + alarms: PageData<AlarmData>;
106 109 alarmSource: Datasource;
107 110
108   - private alarmSearchStatusValue: AlarmSearchStatus;
  111 + /* private alarmSearchStatusValue: AlarmSearchStatus;
109 112
110 113 set alarmSearchStatus(value: AlarmSearchStatus) {
111 114 if (this.alarmSearchStatusValue !== value) {
... ... @@ -116,12 +119,14 @@ export class WidgetSubscription implements IWidgetSubscription {
116 119
117 120 get alarmSearchStatus(): AlarmSearchStatus {
118 121 return this.alarmSearchStatusValue;
119   - }
  122 + }*/
  123 +
  124 + alarmDataListener: AlarmDataListener;
120 125
121   - alarmsPollingInterval: number;
  126 +/* alarmsPollingInterval: number;
122 127 alarmsMaxCountLoad: number;
123 128 alarmsFetchSize: number;
124   - alarmSourceListener: AlarmSourceListener;
  129 + alarmSourceListener: AlarmSourceListener;*/
125 130
126 131 loadingData: boolean;
127 132
... ... @@ -181,7 +186,7 @@ export class WidgetSubscription implements IWidgetSubscription {
181 186 this.callbacks.dataLoading = this.callbacks.dataLoading || (() => {});
182 187 this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || (() => {});
183 188 this.alarmSource = options.alarmSource;
184   - this.alarmSearchStatusValue = isDefined(options.alarmSearchStatus) ?
  189 + /*this.alarmSearchStatusValue = isDefined(options.alarmSearchStatus) ?
185 190 options.alarmSearchStatus : AlarmSearchStatus.ANY;
186 191 this.alarmsPollingInterval = isDefined(options.alarmsPollingInterval) ?
187 192 options.alarmsPollingInterval : 5000;
... ... @@ -189,8 +194,10 @@ export class WidgetSubscription implements IWidgetSubscription {
189 194 options.alarmsMaxCountLoad : 0;
190 195 this.alarmsFetchSize = isDefined(options.alarmsFetchSize) ?
191 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 201 this.originalTimewindow = null;
195 202 this.timeWindow = {};
196 203 this.useDashboardTimewindow = options.useDashboardTimewindow;
... ... @@ -290,7 +297,7 @@ export class WidgetSubscription implements IWidgetSubscription {
290 297 if (this.targetDeviceId) {
291 298 this.rpcEnabled = true;
292 299 } else {
293   - this.rpcEnabled = this.ctx.utils.widgetEditMode ? true : false;
  300 + this.rpcEnabled = this.ctx.utils.widgetEditMode;
294 301 }
295 302 this.hasResolvedData = this.rpcEnabled;
296 303 this.callbacks.rpcStateChanged(this);
... ... @@ -317,7 +324,7 @@ export class WidgetSubscription implements IWidgetSubscription {
317 324 if (this.targetDeviceId) {
318 325 this.rpcEnabled = true;
319 326 } else {
320   - this.rpcEnabled = this.ctx.utils.widgetEditMode ? true : false;
  327 + this.rpcEnabled = this.ctx.utils.widgetEditMode;
321 328 }
322 329 this.hasResolvedData = true;
323 330 this.callbacks.rpcStateChanged(this);
... ... @@ -356,6 +363,7 @@ export class WidgetSubscription implements IWidgetSubscription {
356 363 }
357 364
358 365 private configureAlarmsData() {
  366 + this.notifyDataLoaded();
359 367 }
360 368
361 369 private initDataSubscription(): Observable<any> {
... ... @@ -482,13 +490,17 @@ export class WidgetSubscription implements IWidgetSubscription {
482 490 entityName = this.targetDeviceName;
483 491 }
484 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 505 } else {
494 506 for (const datasource of this.datasources) {
... ... @@ -522,7 +534,6 @@ export class WidgetSubscription implements IWidgetSubscription {
522 534 } else {
523 535 return this.checkSubscriptions(aliasIds);
524 536 }
525   - return false;
526 537 }
527 538
528 539 onFiltersChanged(filterIds: Array<string>): boolean {
... ... @@ -573,12 +584,6 @@ export class WidgetSubscription implements IWidgetSubscription {
573 584 return false;
574 585 }
575 586
576   - private onAlarmSearchStatusChanged() {
577   - if (this.type === widgetType.alarm) {
578   - this.update();
579   - }
580   - }
581   -
582 587 updateDataVisibility(index: number): void {
583 588 if (this.displayLegend) {
584 589 const hidden = this.legendData.keys[index].dataKey.hidden;
... ... @@ -752,11 +757,12 @@ export class WidgetSubscription implements IWidgetSubscription {
752 757 }
753 758
754 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 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 865 this.dataSubscribe();
832 866 }
833 867 }
... ... @@ -858,7 +892,7 @@ export class WidgetSubscription implements IWidgetSubscription {
858 892 }
859 893 }
860 894
861   - private alarmsSubscribe() {
  895 + /* private alarmsSubscribe() {
862 896 this.notifyDataLoading();
863 897 if (this.timeWindowConfig) {
864 898 this.updateRealtimeSubscription();
... ... @@ -875,9 +909,10 @@ export class WidgetSubscription implements IWidgetSubscription {
875 909 alarmsFetchSize: this.alarmsFetchSize,
876 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 917 let forceUpdate = false;
883 918 if (this.alarmSource.unresolvedStateEntity ||
... ... @@ -889,7 +924,7 @@ export class WidgetSubscription implements IWidgetSubscription {
889 924 this.notifyDataLoaded();
890 925 this.onDataUpdated();
891 926 }
892   - }
  927 + } */
893 928
894 929
895 930 unsubscribe() {
... ... @@ -910,33 +945,62 @@ export class WidgetSubscription implements IWidgetSubscription {
910 945 }
911 946
912 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 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 958 private checkAlarmSource(aliasIds: Array<string>): boolean {
928 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 967 private checkAlarmSourceFilters(filterIds: Array<string>): boolean {
936 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 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 1063 }
1000 1064 );
1001 1065 },
1002   - (err) => {
  1066 + () => {
1003 1067 this.notifyDataLoaded();
1004 1068 }
1005 1069 );
... ... @@ -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 1098 private notifyDataLoaded() {
1040 1099 this.loadingData = false;
1041 1100 this.callbacks.dataLoading(this);
... ... @@ -1100,23 +1159,21 @@ export class WidgetSubscription implements IWidgetSubscription {
1100 1159 const datasources = pageData.data.map((entityData, index) =>
1101 1160 this.entityDataToDatasource(datasource, entityData, index)
1102 1161 );
1103   - const datasourcesPage: PageData<Datasource> = {
  1162 + this.datasourcePages[datasourceIndex] = {
1104 1163 data: datasources,
1105 1164 hasNext: pageData.hasNext,
1106 1165 totalElements: pageData.totalElements,
1107 1166 totalPages: pageData.totalPages
1108 1167 };
1109   - this.datasourcePages[datasourceIndex] = datasourcesPage;
1110 1168 const datasourceData = datasources.map((datasourceElement, index) =>
1111 1169 this.entityDataToDatasourceData(datasourceElement, data[index])
1112 1170 );
1113   - const datasourceDataPage: PageData<Array<DatasourceData>> = {
  1171 + this.dataPages[datasourceIndex] = {
1114 1172 data: datasourceData,
1115 1173 hasNext: pageData.hasNext,
1116 1174 totalElements: pageData.totalElements,
1117 1175 totalPages: pageData.totalPages
1118 1176 };
1119   - this.dataPages[datasourceIndex] = datasourceDataPage;
1120 1177 if (datasource.type === DatasourceType.entity &&
1121 1178 pageData.hasNext && pageLink.pageSize > 1) {
1122 1179 if (this.warnOnPageDataOverflow) {
... ... @@ -1215,8 +1272,8 @@ export class WidgetSubscription implements IWidgetSubscription {
1215 1272
1216 1273 private entityDataToDatasourceData(datasource: Datasource, data: Array<DataSetHolder>): Array<DatasourceData> {
1217 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 1277 if (this.comparisonEnabled && dataKey.isAdditional && dataKey.settings.comparisonSettings.comparisonValuesLabel) {
1221 1278 dataKey.label = createLabelFromDatasource(datasource, dataKey.settings.comparisonSettings.comparisonValuesLabel);
1222 1279 } else {
... ... @@ -1242,7 +1299,7 @@ export class WidgetSubscription implements IWidgetSubscription {
1242 1299 const newDatasource = deepClone(configDatasource);
1243 1300 const entityInfo = entityDataToEntityInfo(entityData);
1244 1301 updateDatasourceFromEntityInfo(newDatasource, entityInfo);
1245   - newDatasource.generated = index > 0 ? true : false;
  1302 + newDatasource.generated = index > 0;
1246 1303 return newDatasource;
1247 1304 }
1248 1305
... ... @@ -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 1346 this.alarms = alarms;
1292 1347 if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) {
1293 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 1357 private updateLegend(dataIndex: number, data: DataSet, detectChanges: boolean) {
... ...
... ... @@ -16,8 +16,10 @@
16 16
17 17 import { Inject, Injectable, NgZone } from '@angular/core';
18 18 import {
  19 + AlarmDataCmd, AlarmDataUnsubscribeCmd,
  20 + AlarmDataUpdate,
19 21 AttributesSubscriptionCmd, EntityDataCmd, EntityDataUnsubscribeCmd, EntityDataUpdate,
20   - GetHistoryCmd, isEntityDataUpdateMsg,
  22 + GetHistoryCmd, isAlarmDataUpdateMsg, isEntityDataUpdateMsg,
21 23 SubscriptionCmd,
22 24 SubscriptionUpdate,
23 25 SubscriptionUpdateMsg,
... ... @@ -107,6 +109,8 @@ export class TelemetryWebsocketService implements TelemetryService {
107 109 this.cmdsWrapper.historyCmds.push(subscriptionCommand);
108 110 } else if (subscriptionCommand instanceof EntityDataCmd) {
109 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 146 const entityDataUnsubscribeCmd = new EntityDataUnsubscribeCmd();
143 147 entityDataUnsubscribeCmd.cmdId = subscriptionCommand.cmdId;
144 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 154 const cmdId = subscriptionCommand.cmdId;
147 155 if (cmdId) {
... ... @@ -281,6 +289,11 @@ export class TelemetryWebsocketService implements TelemetryService {
281 289 if (subscriber) {
282 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 297 } else if (message.subscriptionId) {
285 298 subscriber = this.subscribersMap.get(message.subscriptionId);
286 299 if (subscriber) {
... ...
... ... @@ -62,7 +62,7 @@
62 62 </mat-toolbar>
63 63 <div fxFlex class="table-container">
64 64 <table mat-table [dataSource]="alarmsDatasource"
65   - matSort [matSortActive]="sortOrderProperty" [matSortDirection]="pageLink.sortDirection()" matSortDisableClear>
  65 + matSort [matSortActive]="sortOrderProperty" [matSortDirection]="pageLinkSortDirection()" matSortDisableClear>
66 66 <ng-container matColumnDef="select" sticky>
67 67 <mat-header-cell *matHeaderCellDef style="width: 30px;">
68 68 <mat-checkbox (change)="$event ? alarmsDatasource.masterToggle() : null"
... ... @@ -125,9 +125,12 @@
125 125 *matRowDef="let alarm; columns: displayedColumns;"
126 126 (click)="onRowClick($event, alarm)"></mat-row>
127 127 </table>
128   - <span [fxShow]="alarmsDatasource.isEmpty() | async"
  128 + <span [fxShow]="(alarmsDatasource.isEmpty() | async) && !alarmsDatasource.dataLoading"
129 129 fxLayoutAlign="center center"
130 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 134 </div>
132 135 <mat-divider *ngIf="displayPagination"></mat-divider>
133 136 <mat-paginator *ngIf="displayPagination"
... ...
... ... @@ -29,21 +29,21 @@ import { PageComponent } from '@shared/components/page.component';
29 29 import { Store } from '@ngrx/store';
30 30 import { AppState } from '@core/core.state';
31 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 33 import { IWidgetSubscription } from '@core/api/widget-api.models';
34 34 import { UtilsService } from '@core/services/utils.service';
35 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 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 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 42 import { emptyPageData, PageData } from '@shared/models/page/page-data';
43 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 45 import { MatPaginator } from '@angular/material/paginator';
46   -import { MatSort } from '@angular/material/sort';
  46 +import { MatSort, SortDirection } from '@angular/material/sort';
47 47 import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
48 48 import {
49 49 CellContentInfo,
... ... @@ -51,14 +51,16 @@ import {
51 51 constructTableCssString,
52 52 DisplayColumn,
53 53 EntityColumn,
54   - fromAlarmColumnDef,
  54 + entityDataSortOrderFromString,
  55 + findColumnByEntityKey,
  56 + findEntityKeyByColumnDef,
  57 + fromEntityColumnDef,
55 58 getAlarmValue,
56 59 getCellContentInfo,
57 60 getCellStyleInfo,
58 61 getColumnWidth,
59 62 TableWidgetDataKeySettings,
60 63 TableWidgetSettings,
61   - toAlarmColumnDef,
62 64 widthStyle
63 65 } from '@home/components/widget/lib/table-widget.models';
64 66 import { ConnectedPosition, Overlay, OverlayConfig, OverlayRef } from '@angular/cdk/overlay';
... ... @@ -69,8 +71,8 @@ import {
69 71 DisplayColumnsPanelData
70 72 } from '@home/components/widget/lib/display-columns-panel.component';
71 73 import {
  74 + AlarmDataInfo,
72 75 alarmFields,
73   - AlarmInfo,
74 76 alarmSeverityColors,
75 77 alarmSeverityTranslations,
76 78 AlarmStatus,
... ... @@ -90,6 +92,15 @@ import { MatDialog } from '@angular/material/dialog';
90 92 import { NULL_UUID } from '@shared/models/id/has-uuid';
91 93 import { DialogService } from '@core/services/dialog.service';
92 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 105 interface AlarmsTableWidgetSettings extends TableWidgetSettings {
95 106 alarmsTitle: string;
... ... @@ -125,7 +136,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
125 136 public displayPagination = true;
126 137 public enableStickyAction = false;
127 138 public pageSizeOptions;
128   - public pageLink: PageLink;
  139 + public pageLink: AlarmDataPageLink;
129 140 public sortOrderProperty: string;
130 141 public textSearchMode = false;
131 142 public columns: Array<EntityColumn> = [];
... ... @@ -189,9 +200,11 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
189 200 private dialogService: DialogService,
190 201 private alarmService: AlarmService) {
191 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 210 ngOnInit(): void {
... ... @@ -232,11 +245,15 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
232 245
233 246 public onDataUpdated() {
234 247 this.ngZone.run(() => {
235   - this.alarmsDatasource.updateAlarms(this.subscription.alarms);
  248 + this.alarmsDatasource.updateAlarms();
236 249 this.ctx.detectChanges();
237 250 });
238 251 }
239 252
  253 + public pageLinkSortDirection(): SortDirection {
  254 + return entityDataPageLinkSortDirection(this.pageLink);
  255 + }
  256 +
240 257 private initializeConfig() {
241 258 this.ctx.widgetActions = [this.searchAction, this.statusFilterAction, this.columnDisplayAction];
242 259
... ... @@ -304,6 +321,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
304 321 this.pageSizeOptions = [this.defaultPageSize, this.defaultPageSize * 2, this.defaultPageSize * 3];
305 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 330 const cssString = constructTableCssString(this.widgetConfig);
308 331 const cssParser = new cssjs();
309 332 cssParser.testMode = false;
... ... @@ -319,9 +342,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
319 342 this.displayedColumns.push('select');
320 343 }
321 344
  345 + const latestDataKeys: Array<DataKey> = [];
  346 +
322 347 if (this.alarmSource) {
323 348 this.alarmSource.dataKeys.forEach((alarmDataKey) => {
324 349 const dataKey: EntityColumn = deepClone(alarmDataKey) as EntityColumn;
  350 + dataKey.entityKey = dataKeyToEntityKey(alarmDataKey);
325 351 dataKey.title = this.utils.customTranslation(dataKey.label, dataKey.label);
326 352 dataKey.def = 'def' + this.columns.length;
327 353 const keySettings: TableWidgetDataKeySettings = dataKey.settings;
... ... @@ -330,19 +356,28 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
330 356 this.contentsInfo[dataKey.def] = getCellContentInfo(keySettings, 'value, alarm, ctx');
331 357 this.columnWidth[dataKey.def] = getColumnWidth(keySettings);
332 358 this.columns.push(dataKey);
  359 +
  360 + if (dataKey.type !== DataKeyType.alarm) {
  361 + latestDataKeys.push(dataKey);
  362 + }
333 363 });
334 364 this.displayedColumns.push(...this.columns.map(column => column.def));
335 365 }
336 366 if (this.settings.defaultSortOrder && this.settings.defaultSortOrder.length) {
337 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 376 if (this.actionCellDescriptors.length) {
343 377 this.displayedColumns.push('actions');
344 378 }
345   - this.alarmsDatasource = new AlarmsDatasource();
  379 +
  380 + this.alarmsDatasource = new AlarmsDatasource(this.subscription, latestDataKeys);
346 381 if (this.enableSelection) {
347 382 this.alarmsDatasource.selectionModeChanged$.subscribe((selectionMode) => {
348 383 const hideTitlePanel = selectionMode || this.textSearchMode;
... ... @@ -467,9 +502,13 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
467 502 } else {
468 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 512 this.ctx.detectChanges();
474 513 }
475 514
... ... @@ -482,7 +521,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
482 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 525 let style: any = {};
487 526 if (alarm && key) {
488 527 const styleInfo = this.stylesInfo[key.def];
... ... @@ -504,7 +543,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
504 543 return style;
505 544 }
506 545
507   - public cellContent(alarm: AlarmInfo, key: EntityColumn): SafeHtml {
  546 + public cellContent(alarm: AlarmDataInfo, key: EntityColumn): SafeHtml {
508 547 if (alarm && key) {
509 548 const contentInfo = this.contentsInfo[key.def];
510 549 const value = getAlarmValue(alarm, key);
... ... @@ -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 567 if ($event) {
529 568 $event.stopPropagation();
530 569 }
... ... @@ -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 584 if (actionDescriptor.details) {
546 585 this.openAlarmDetails($event, alarm);
547 586 } else if (actionDescriptor.acknowledge) {
... ... @@ -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 605 if (actionDescriptor.acknowledge) {
567 606 return (alarm.status === AlarmStatus.ACTIVE_UNACK ||
568 607 alarm.status === AlarmStatus.CLEARED_UNACK);
... ... @@ -573,7 +612,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
573 612 return true;
574 613 }
575 614
576   - private openAlarmDetails($event: Event, alarm: AlarmInfo) {
  615 + private openAlarmDetails($event: Event, alarm: AlarmDataInfo) {
577 616 if ($event) {
578 617 $event.stopPropagation();
579 618 }
... ... @@ -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 642 if ($event) {
604 643 $event.stopPropagation();
605 644 }
... ... @@ -626,12 +665,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
626 665 $event.stopPropagation();
627 666 }
628 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 674 this.dialogService.confirm(
636 675 title,
637 676 content,
... ... @@ -641,8 +680,8 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
641 680 if (res) {
642 681 if (res) {
643 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 686 forkJoin(tasks).subscribe(() => {
648 687 this.alarmsDatasource.clearSelection();
... ... @@ -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 698 if ($event) {
660 699 $event.stopPropagation();
661 700 }
... ... @@ -682,12 +721,12 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
682 721 $event.stopPropagation();
683 722 }
684 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 730 this.dialogService.confirm(
692 731 title,
693 732 content,
... ... @@ -697,8 +736,8 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
697 736 if (res) {
698 737 if (res) {
699 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 742 forkJoin(tasks).subscribe(() => {
704 743 this.alarmsDatasource.clearSelection();
... ... @@ -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 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 821 return this.alarmsSubject.asObservable();
781 822 }
782 823
... ... @@ -785,51 +826,63 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
785 826 this.pageDataSubject.complete();
786 827 }
787 828
788   - loadAlarms(pageLink: PageLink) {
  829 + loadAlarms(pageLink: AlarmDataPageLink, sortOrderLabel: string, keyFilters: KeyFilter[]) {
789 830 if (this.selection.hasValue()) {
790 831 this.selection.clear();
791 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 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 852 this.selection.deselect(...toRemove);
828 853 if (this.selection.isEmpty()) {
829 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 888 isAllSelected(): Observable<boolean> {
... ... @@ -851,16 +904,16 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
851 904 );
852 905 }
853 906
854   - toggleSelection(alarm: AlarmInfo) {
  907 + toggleSelection(alarm: AlarmDataInfo) {
855 908 const hasValue = this.selection.hasValue();
856   - this.selection.toggle(alarm);
  909 + this.selection.toggle(alarm.id.id);
857 910 if (hasValue !== this.selection.hasValue()) {
858 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 919 clearSelection() {
... ... @@ -881,7 +934,7 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
881 934 }
882 935 } else {
883 936 alarms.forEach(row => {
884   - this.selection.select(row);
  937 + this.selection.select(row.id.id);
885 938 });
886 939 if (numSelected === 0) {
887 940 this.onSelectionModeChanged(true);
... ... @@ -892,7 +945,7 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
892 945 ).subscribe();
893 946 }
894 947
895   - public toggleCurrentAlarm(alarm: AlarmInfo): boolean {
  948 + public toggleCurrentAlarm(alarm: AlarmDataInfo): boolean {
896 949 if (this.currentAlarm !== alarm) {
897 950 this.currentAlarm = alarm;
898 951 return true;
... ... @@ -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 958 return (this.currentAlarm && alarm && this.currentAlarm.id && alarm.id) &&
906 959 (this.currentAlarm.id.id === alarm.id.id);
907 960 }
... ... @@ -909,10 +962,4 @@ class AlarmsDatasource implements DataSource<AlarmInfo> {
909 962 private onSelectionModeChanged(selectionMode: boolean) {
910 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 77 DisplayColumnsPanelData
78 78 } from '@home/components/widget/lib/display-columns-panel.component';
79 79 import {
  80 + dataKeyToEntityKey,
80 81 Direction,
81 82 EntityDataPageLink,
82 83 entityDataPageLinkSortDirection,
... ... @@ -205,7 +206,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
205 206
206 207 public onDataUpdated() {
207 208 this.ngZone.run(() => {
208   - this.entityDatasource.dataUpdated(); // .updateEntitiesData(this.subscription.data);
  209 + this.entityDatasource.dataUpdated();
209 210 this.ctx.detectChanges();
210 211 });
211 212 }
... ... @@ -339,19 +340,9 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
339 340 if (datasource && datasource.dataKeys) {
340 341 datasource.dataKeys.forEach((entityDataKey) => {
341 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 344 if (dataKey.type === DataKeyType.function) {
347 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 347 dataKeys.push(dataKey);
357 348
... ...
... ... @@ -17,9 +17,10 @@
17 17 import { EntityId } from '@shared/models/id/entity-id';
18 18 import { DataKey, WidgetConfig } from '@shared/models/widget.models';
19 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 21 import * as tinycolor_ from 'tinycolor2';
22 22 import { Direction, EntityDataSortOrder, EntityKey } from '@shared/models/query/query.models';
  23 +import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
23 24
24 25 const tinycolor = tinycolor_;
25 26
... ... @@ -120,7 +121,15 @@ export function findColumn(searchProperty: string, searchValue: string, columns:
120 121 }
121 122
122 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 135 export function findColumnByDef(def: string, columns: EntityColumn[]): EntityColumn {
... ... @@ -160,12 +169,12 @@ export function getEntityValue(entity: any, key: DataKey): any {
160 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 173 const alarmField = alarmFields[key.name];
165 174 if (alarmField) {
166 175 return getDescendantProp(alarm, alarmField.value);
167 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 94 import { EntityDataService } from '@core/api/entity-data.service';
95 95 import { TranslateService } from '@ngx-translate/core';
96 96 import { NotificationType } from '@core/notification/notification.models';
  97 +import { AlarmDataService } from '@core/api/alarm-data.service';
97 98
98 99 @Component({
99 100 selector: 'tb-widget',
... ... @@ -167,9 +168,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
167 168 private timeService: TimeService,
168 169 private deviceService: DeviceService,
169 170 private entityService: EntityService,
170   - private alarmService: AlarmService,
171 171 private dashboardService: DashboardService,
172 172 private entityDataService: EntityDataService,
  173 + private alarmDataService: AlarmDataService,
173 174 private translate: TranslateService,
174 175 private utils: UtilsService,
175 176 private raf: RafService,
... ... @@ -300,9 +301,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
300 301 this.subscriptionContext = new WidgetSubscriptionContext(this.widgetContext.dashboard);
301 302 this.subscriptionContext.timeService = this.timeService;
302 303 this.subscriptionContext.deviceService = this.deviceService;
303   - this.subscriptionContext.alarmService = this.alarmService;
304 304 this.subscriptionContext.translate = this.translate;
305 305 this.subscriptionContext.entityDataService = this.entityDataService;
  306 + this.subscriptionContext.alarmDataService = this.alarmDataService;
306 307 this.subscriptionContext.utils = this.utils;
307 308 this.subscriptionContext.raf = this.raf;
308 309 this.subscriptionContext.widgetUtils = this.widgetContext.utils;
... ... @@ -901,14 +902,14 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI
901 902 };
902 903 if (this.widget.type === widgetType.alarm) {
903 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 906 this.widget.config.alarmSearchStatus : AlarmSearchStatus.ANY;
906 907 options.alarmsPollingInterval = isDefined(this.widget.config.alarmsPollingInterval) ?
907 908 this.widget.config.alarmsPollingInterval * 1000 : 5000;
908 909 options.alarmsMaxCountLoad = isDefined(this.widget.config.alarmsMaxCountLoad) ?
909 910 this.widget.config.alarmsMaxCountLoad : 0;
910 911 options.alarmsFetchSize = isDefined(this.widget.config.alarmsFetchSize) ?
911   - this.widget.config.alarmsFetchSize : 100;
  912 + this.widget.config.alarmsFetchSize : 100;*/
912 913 } else {
913 914 options.datasources = deepClone(this.widget.config.datasources);
914 915 }
... ...
... ... @@ -103,6 +103,10 @@ export interface AlarmInfo extends Alarm {
103 103 originatorName: string;
104 104 }
105 105
  106 +export interface AlarmDataInfo extends AlarmInfo {
  107 + [key: string]: any;
  108 +}
  109 +
106 110 export const simulatedAlarm: AlarmInfo = {
107 111 id: new AlarmId(NULL_UUID),
108 112 tenantId: new TenantId(NULL_UUID),
... ...
... ... @@ -20,10 +20,11 @@ import { SortDirection } from '@angular/material/sort';
20 20 import { DataKeyType } from '@shared/models/telemetry/telemetry.models';
21 21 import { EntityInfo } from '@shared/models/entity.models';
22 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 24 import { PageData } from '@shared/models/page/page-data';
25 25 import { isDefined, isEqual } from '@core/utils';
26 26 import { TranslateService } from '@ngx-translate/core';
  27 +import { AlarmInfo, AlarmSearchStatus, AlarmSeverity } from '../alarm.models';
27 28
28 29 export enum EntityKeyType {
29 30 ATTRIBUTE = 'ATTRIBUTE',
... ... @@ -31,7 +32,8 @@ export enum EntityKeyType {
31 32 SHARED_ATTRIBUTE = 'SHARED_ATTRIBUTE',
32 33 SERVER_ATTRIBUTE = 'SERVER_ATTRIBUTE',
33 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 39 export const entityKeyTypeTranslationMap = new Map<EntityKeyType, string>(
... ... @@ -53,6 +55,23 @@ export function entityKeyTypeToDataKeyType(entityKeyType: EntityKeyType): DataKe
53 55 return DataKeyType.timeseries;
54 56 case EntityKeyType.ENTITY_FIELD:
55 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 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 91 export enum EntityKeyValueType {
65 92 STRING = 'STRING',
66 93 NUMERIC = 'NUMERIC',
... ... @@ -479,6 +506,16 @@ export interface EntityDataPageLink {
479 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 519 export function entityDataPageLinkSortDirection(pageLink: EntityDataPageLink): SortDirection {
483 520 if (pageLink.sortOrder) {
484 521 return (pageLink.sortOrder.direction + '').toLowerCase() as SortDirection;
... ... @@ -508,13 +545,19 @@ export interface EntityCountQuery {
508 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 550 entityFields?: Array<EntityKey>;
514 551 latestValues?: Array<EntityKey>;
515 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 561 export interface TsValue {
519 562 ts: number;
520 563 value: string;
... ... @@ -526,6 +569,11 @@ export interface EntityData {
526 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 577 export function entityPageDataChanged(prevPageData: PageData<EntityData>, nextPageData: PageData<EntityData>): boolean {
530 578 const prevIds = prevPageData.data.map((entityData) => entityData.entityId.id);
531 579 const nextIds = nextPageData.data.map((entityData) => entityData.entityId.id);
... ...
... ... @@ -21,7 +21,7 @@ import { Observable, ReplaySubject, Subject } from 'rxjs';
21 21 import { EntityId } from '@shared/models/id/entity-id';
22 22 import { map } from 'rxjs/operators';
23 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 25 import { PageData } from '@shared/models/page/page-data';
26 26
27 27 export enum DataKeyType {
... ... @@ -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 177 export class EntityDataUnsubscribeCmd implements WebsocketCmd {
169 178 cmdId: number;
170 179 }
171 180
  181 +export class AlarmDataUnsubscribeCmd implements WebsocketCmd {
  182 + cmdId: number;
  183 +}
  184 +
172 185 export class TelemetryPluginCmdsWrapper {
173 186 attrSubCmds: Array<AttributesSubscriptionCmd>;
174 187 tsSubCmds: Array<TimeseriesSubscriptionCmd>;
175 188 historyCmds: Array<GetHistoryCmd>;
176 189 entityDataCmds: Array<EntityDataCmd>;
177 190 entityDataUnsubscribeCmds: Array<EntityDataUnsubscribeCmd>;
  191 + alarmDataCmds: Array<AlarmDataCmd>;
  192 + alarmDataUnsubscribeCmds: Array<AlarmDataUnsubscribeCmd>;
178 193
179 194 constructor() {
180 195 this.attrSubCmds = [];
... ... @@ -182,6 +197,8 @@ export class TelemetryPluginCmdsWrapper {
182 197 this.historyCmds = [];
183 198 this.entityDataCmds = [];
184 199 this.entityDataUnsubscribeCmds = [];
  200 + this.alarmDataCmds = [];
  201 + this.alarmDataUnsubscribeCmds = [];
185 202 }
186 203
187 204 public hasCommands(): boolean {
... ... @@ -189,7 +206,9 @@ export class TelemetryPluginCmdsWrapper {
189 206 this.historyCmds.length > 0 ||
190 207 this.attrSubCmds.length > 0 ||
191 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 214 public clear() {
... ... @@ -198,6 +217,8 @@ export class TelemetryPluginCmdsWrapper {
198 217 this.historyCmds.length = 0;
199 218 this.entityDataCmds.length = 0;
200 219 this.entityDataUnsubscribeCmds.length = 0;
  220 + this.alarmDataCmds.length = 0;
  221 + this.alarmDataUnsubscribeCmds.length = 0;
201 222 }
202 223
203 224 public preparePublishCommands(maxCommands: number): TelemetryPluginCmdsWrapper {
... ... @@ -212,6 +233,10 @@ export class TelemetryPluginCmdsWrapper {
212 233 preparedWrapper.entityDataCmds = this.popCmds(this.entityDataCmds, leftCount);
213 234 leftCount -= preparedWrapper.entityDataCmds.length;
214 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 240 return preparedWrapper;
216 241 }
217 242
... ... @@ -239,18 +264,38 @@ export interface SubscriptionUpdateMsg extends SubscriptionDataHolder {
239 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 273 cmdId: number;
244   - data?: PageData<EntityData>;
245   - update?: Array<EntityData>;
  274 + data?: PageData<T>;
  275 + update?: Array<T>;
246 276 errorCode: number;
247 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 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 301 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 351 cmdId: number;
307 352 errorCode: number;
308 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 359 this.cmdId = msg.cmdId;
314 360 this.errorCode = msg.errorCode;
315 361 this.errorMsg = msg.errorMsg;
316 362 this.data = msg.data;
317 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 387
329 388 private dataSubject = new ReplaySubject<SubscriptionUpdate>(1);
330 389 private entityDataSubject = new ReplaySubject<EntityDataUpdate>(1);
  390 + private alarmDataSubject = new ReplaySubject<AlarmDataUpdate>(1);
331 391 private reconnectSubject = new Subject();
332 392
333 393 private zone: NgZone;
... ... @@ -336,6 +396,7 @@ export class TelemetrySubscriber {
336 396
337 397 public data$ = this.dataSubject.asObservable();
338 398 public entityData$ = this.entityDataSubject.asObservable();
  399 + public alarmData$ = this.alarmDataSubject.asObservable();
339 400 public reconnect$ = this.reconnectSubject.asObservable();
340 401
341 402 public static createEntityAttributesSubscription(telemetryService: TelemetryService,
... ... @@ -379,6 +440,7 @@ export class TelemetrySubscriber {
379 440 public complete() {
380 441 this.dataSubject.complete();
381 442 this.entityDataSubject.complete();
  443 + this.alarmDataSubject.complete();
382 444 this.reconnectSubject.complete();
383 445 }
384 446
... ... @@ -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 493 public onReconnected() {
420 494 this.reconnectSubject.next();
421 495 }
... ...