Showing
18 changed files
with
771 additions
and
217 deletions
... | ... | @@ -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, | ... | ... |
application/src/main/java/org/thingsboard/server/service/telemetry/cmd/v2/DataUpdateType.java
0 → 100644
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 | } | ... | ... |