/// /// Copyright © 2016-2020 The Thingsboard Authors /// /// Licensed under the Apache License, Version 2.0 (the "License"); /// you may not use this file except in compliance with the License. /// You may obtain a copy of the License at /// /// http://www.apache.org/licenses/LICENSE-2.0 /// /// Unless required by applicable law or agreed to in writing, software /// distributed under the License is distributed on an "AS IS" BASIS, /// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. /// See the License for the specific language governing permissions and /// limitations under the License. /// import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models'; import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs'; import { DataKey, Datasource, DatasourceType } from '@app/shared/models/widget.models'; import { deepClone, isEqual } from '@core/utils'; import { EntityService } from '@core/http/entity.service'; import { UtilsService } from '@core/services/utils.service'; import { EntityAliases } from '@shared/models/alias.models'; import { EntityInfo } from '@shared/models/entity.models'; import { map } from 'rxjs/operators'; export class AliasController implements IAliasController { entityAliasesChangedSubject = new Subject>(); entityAliasesChanged: Observable> = this.entityAliasesChangedSubject.asObservable(); private entityAliasResolvedSubject = new Subject(); entityAliasResolved: Observable = this.entityAliasResolvedSubject.asObservable(); entityAliases: EntityAliases; resolvedAliases: {[aliasId: string]: AliasInfo} = {}; resolvedAliasesObservable: {[aliasId: string]: Observable} = {}; resolvedAliasesToStateEntities: {[aliasId: string]: StateEntityInfo} = {}; constructor(private utils: UtilsService, private entityService: EntityService, private stateControllerHolder: StateControllerHolder, private origEntityAliases: EntityAliases) { this.entityAliases = deepClone(this.origEntityAliases); } updateEntityAliases(newEntityAliases: EntityAliases) { const changedAliasIds: Array = []; for (const aliasId of Object.keys(newEntityAliases)) { const newEntityAlias = newEntityAliases[aliasId]; const prevEntityAlias = this.entityAliases[aliasId]; if (!isEqual(newEntityAlias, prevEntityAlias)) { changedAliasIds.push(aliasId); this.setAliasUnresolved(aliasId); } } for (const aliasId of Object.keys(this.entityAliases)) { if (!newEntityAliases[aliasId]) { changedAliasIds.push(aliasId); this.setAliasUnresolved(aliasId); } } this.entityAliases = deepClone(newEntityAliases); if (changedAliasIds.length) { this.entityAliasesChangedSubject.next(changedAliasIds); } } updateAliases(aliasIds?: Array) { if (!aliasIds) { aliasIds = []; for (const aliasId of Object.keys(this.resolvedAliases)) { aliasIds.push(aliasId); } } const tasks: Observable[] = []; for (const aliasId of aliasIds) { this.setAliasUnresolved(aliasId); tasks.push(this.getAliasInfo(aliasId)); } forkJoin(tasks).subscribe(() => { this.entityAliasesChangedSubject.next(aliasIds); }); } dashboardStateChanged() { const changedAliasIds: Array = []; for (const aliasId of Object.keys(this.resolvedAliasesToStateEntities)) { const stateEntityInfo = this.resolvedAliasesToStateEntities[aliasId]; const newEntityId = this.stateControllerHolder().getEntityId(stateEntityInfo.entityParamName); const prevEntityId = stateEntityInfo.entityId; if (!isEqual(newEntityId, prevEntityId)) { changedAliasIds.push(aliasId); this.setAliasUnresolved(aliasId); } } if (changedAliasIds.length) { this.entityAliasesChangedSubject.next(changedAliasIds); } } setAliasUnresolved(aliasId: string) { delete this.resolvedAliases[aliasId]; delete this.resolvedAliasesObservable[aliasId]; delete this.resolvedAliasesToStateEntities[aliasId]; } getEntityAliases(): EntityAliases { return this.entityAliases; } getEntityAliasId(aliasName: string): string { for (const aliasId of Object.keys(this.entityAliases)) { const alias = this.entityAliases[aliasId]; if (alias.alias === aliasName) { return aliasId; } } return null; } getAliasInfo(aliasId: string): Observable { const aliasInfo = this.resolvedAliases[aliasId]; if (aliasInfo) { return of(aliasInfo); } else if (this.resolvedAliasesObservable[aliasId]) { return this.resolvedAliasesObservable[aliasId]; } else { const resolvedAliasSubject = new ReplaySubject(); this.resolvedAliasesObservable[aliasId] = resolvedAliasSubject.asObservable(); const entityAlias = this.entityAliases[aliasId]; if (entityAlias) { this.entityService.resolveAlias(entityAlias, this.stateControllerHolder().getStateParams()).subscribe( (resolvedAliasInfo) => { this.resolvedAliases[aliasId] = resolvedAliasInfo; delete this.resolvedAliasesObservable[aliasId]; if (resolvedAliasInfo.stateEntity) { const stateEntityInfo: StateEntityInfo = { entityParamName: resolvedAliasInfo.entityParamName, entityId: this.stateControllerHolder().getEntityId(resolvedAliasInfo.entityParamName) }; this.resolvedAliasesToStateEntities[aliasId] = stateEntityInfo; } this.entityAliasResolvedSubject.next(aliasId); resolvedAliasSubject.next(resolvedAliasInfo); resolvedAliasSubject.complete(); }, () => { resolvedAliasSubject.error(null); delete this.resolvedAliasesObservable[aliasId]; } ); } else { resolvedAliasSubject.error(null); const res = this.resolvedAliasesObservable[aliasId]; delete this.resolvedAliasesObservable[aliasId]; return res; } return this.resolvedAliasesObservable[aliasId]; } } private resolveDatasource(datasource: Datasource, isSingle?: boolean): Observable> { if (datasource.type === DatasourceType.entity) { if (datasource.entityAliasId) { return this.getAliasInfo(datasource.entityAliasId).pipe( map((aliasInfo) => { datasource.aliasName = aliasInfo.alias; if (aliasInfo.resolveMultiple && !isSingle) { let newDatasource: Datasource; const resolvedEntities = aliasInfo.resolvedEntities; if (resolvedEntities && resolvedEntities.length) { const datasources: Array = []; for (let i = 0; i < resolvedEntities.length; i++) { const resolvedEntity = resolvedEntities[i]; newDatasource = deepClone(datasource); if (resolvedEntity.origEntity) { newDatasource.entity = deepClone(resolvedEntity.origEntity); } else { newDatasource.entity = {}; } newDatasource.entityId = resolvedEntity.id; newDatasource.entityType = resolvedEntity.entityType; newDatasource.entityName = resolvedEntity.name; newDatasource.entityLabel = resolvedEntity.label; newDatasource.entityDescription = resolvedEntity.entityDescription; newDatasource.name = resolvedEntity.name; newDatasource.generated = i > 0 ? true : false; datasources.push(newDatasource); } return datasources; } else { if (aliasInfo.stateEntity) { newDatasource = deepClone(datasource); newDatasource.unresolvedStateEntity = true; return [newDatasource]; } else { return []; // throw new Error('Unable to resolve datasource.'); } } } else { const entity = aliasInfo.currentEntity; if (entity) { if (entity.origEntity) { datasource.entity = deepClone(entity.origEntity); } else { datasource.entity = {}; } datasource.entityId = entity.id; datasource.entityType = entity.entityType; datasource.entityName = entity.name; datasource.entityLabel = entity.label; datasource.name = entity.name; datasource.entityDescription = entity.entityDescription; return [datasource]; } else { if (aliasInfo.stateEntity) { datasource.unresolvedStateEntity = true; return [datasource]; } else { return []; // throw new Error('Unable to resolve datasource.'); } } } }) ); } else { datasource.aliasName = datasource.entityName; datasource.name = datasource.entityName; return of([datasource]); } } else { return of([datasource]); } } resolveAlarmSource(alarmSource: Datasource): Observable { return this.resolveDatasource(alarmSource, true).pipe( map((datasources) => { const datasource = datasources && datasources.length ? datasources[0] : deepClone(alarmSource); if (datasource.type === DatasourceType.function) { let name: string; if (datasource.name && datasource.name.length) { name = datasource.name; } else { name = DatasourceType.function; } datasource.name = name; datasource.aliasName = name; datasource.entityName = name; } else if (datasource.unresolvedStateEntity) { datasource.name = 'Unresolved'; datasource.entityName = 'Unresolved'; } return datasource; }) ); } resolveDatasources(datasources: Array): Observable> { const newDatasources = deepClone(datasources); const observables = new Array>>(); newDatasources.forEach((datasource) => { observables.push(this.resolveDatasource(datasource)); }); return forkJoin(observables).pipe( map((arrayOfDatasources) => { const result = new Array(); arrayOfDatasources.forEach((datasourcesArray) => { result.push(...datasourcesArray); }); result.sort((d1, d2) => { const i1 = d1.generated ? 1 : 0; const i2 = d2.generated ? 1 : 0; return i1 - i2; }); let index = 0; let functionIndex = 0; result.forEach((datasource) => { if (datasource.type === DatasourceType.function) { let name: string; if (datasource.name && datasource.name.length) { name = datasource.name; } else { functionIndex++; name = DatasourceType.function; if (functionIndex > 1) { name += ' ' + functionIndex; } } datasource.name = name; datasource.aliasName = name; datasource.entityName = name; } else if (datasource.unresolvedStateEntity) { datasource.name = 'Unresolved'; datasource.entityName = 'Unresolved'; } datasource.dataKeys.forEach((dataKey) => { if (datasource.generated) { dataKey._hash = Math.random(); dataKey.color = this.utils.getMaterialColor(index); } index++; }); this.updateDatasourceKeyLabels(datasource); }); return result; }) ); } private updateDatasourceKeyLabels(datasource: Datasource) { datasource.dataKeys.forEach((dataKey) => { this.updateDataKeyLabel(dataKey, datasource); }); } private updateDataKeyLabel(dataKey: DataKey, datasource: Datasource) { if (!dataKey.pattern) { dataKey.pattern = deepClone(dataKey.label); } dataKey.label = this.utils.createLabelFromDatasource(datasource, dataKey.pattern); } getInstantAliasInfo(aliasId: string): AliasInfo { return this.resolvedAliases[aliasId]; } updateCurrentAliasEntity(aliasId: string, currentEntity: EntityInfo) { const aliasInfo = this.resolvedAliases[aliasId]; if (aliasInfo) { const prevCurrentEntity = aliasInfo.currentEntity; if (!isEqual(currentEntity, prevCurrentEntity)) { aliasInfo.currentEntity = currentEntity; this.entityAliasesChangedSubject.next([aliasId]); } } } }