2019-09-10 15:12:10 +03:00
|
|
|
///
|
2020-02-20 10:26:43 +02:00
|
|
|
/// Copyright © 2016-2020 The Thingsboard Authors
|
2019-09-10 15:12:10 +03:00
|
|
|
///
|
|
|
|
|
/// 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.
|
|
|
|
|
///
|
|
|
|
|
|
2020-01-22 20:05:30 +02:00
|
|
|
import { AliasInfo, IAliasController, StateControllerHolder, StateEntityInfo } from '@core/api/widget-api.models';
|
2019-10-11 19:22:03 +03:00
|
|
|
import { forkJoin, Observable, of, ReplaySubject, Subject } from 'rxjs';
|
|
|
|
|
import { DataKey, Datasource, DatasourceType } from '@app/shared/models/widget.models';
|
2020-02-26 18:15:04 +02:00
|
|
|
import { deepClone, isEqual } from '@core/utils';
|
2019-09-19 20:10:52 +03:00
|
|
|
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';
|
2020-01-22 20:05:30 +02:00
|
|
|
import { map } from 'rxjs/operators';
|
2019-09-19 20:10:52 +03:00
|
|
|
|
|
|
|
|
export class AliasController implements IAliasController {
|
|
|
|
|
|
|
|
|
|
private entityAliasesChangedSubject = new Subject<Array<string>>();
|
|
|
|
|
entityAliasesChanged: Observable<Array<string>> = this.entityAliasesChangedSubject.asObservable();
|
|
|
|
|
|
|
|
|
|
private entityAliasResolvedSubject = new Subject<string>();
|
|
|
|
|
entityAliasResolved: Observable<string> = this.entityAliasResolvedSubject.asObservable();
|
|
|
|
|
|
|
|
|
|
entityAliases: EntityAliases;
|
|
|
|
|
|
|
|
|
|
resolvedAliases: {[aliasId: string]: AliasInfo} = {};
|
2019-10-11 19:22:03 +03:00
|
|
|
resolvedAliasesObservable: {[aliasId: string]: Observable<AliasInfo>} = {};
|
2019-09-19 20:10:52 +03:00
|
|
|
|
2019-10-11 19:22:03 +03:00
|
|
|
resolvedAliasesToStateEntities: {[aliasId: string]: StateEntityInfo} = {};
|
2019-09-19 20:10:52 +03:00
|
|
|
|
|
|
|
|
constructor(private utils: UtilsService,
|
|
|
|
|
private entityService: EntityService,
|
2019-10-11 19:22:03 +03:00
|
|
|
private stateControllerHolder: StateControllerHolder,
|
2019-09-19 20:10:52 +03:00
|
|
|
private origEntityAliases: EntityAliases) {
|
|
|
|
|
this.entityAliases = deepClone(this.origEntityAliases);
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-11 19:22:03 +03:00
|
|
|
|
|
|
|
|
updateEntityAliases(newEntityAliases: EntityAliases) {
|
|
|
|
|
const changedAliasIds: Array<string> = [];
|
|
|
|
|
for (const aliasId of Object.keys(newEntityAliases)) {
|
|
|
|
|
const newEntityAlias = newEntityAliases[aliasId];
|
|
|
|
|
const prevEntityAlias = this.entityAliases[aliasId];
|
2020-02-26 18:15:04 +02:00
|
|
|
if (!isEqual(newEntityAlias, prevEntityAlias)) {
|
2019-10-11 19:22:03 +03:00
|
|
|
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);
|
|
|
|
|
}
|
2019-09-19 20:10:52 +03:00
|
|
|
}
|
|
|
|
|
|
2019-10-11 19:22:03 +03:00
|
|
|
dashboardStateChanged() {
|
|
|
|
|
const changedAliasIds: Array<string> = [];
|
|
|
|
|
for (const aliasId of Object.keys(this.resolvedAliasesToStateEntities)) {
|
|
|
|
|
const stateEntityInfo = this.resolvedAliasesToStateEntities[aliasId];
|
|
|
|
|
const newEntityId = this.stateControllerHolder().getEntityId(stateEntityInfo.entityParamName);
|
|
|
|
|
const prevEntityId = stateEntityInfo.entityId;
|
2020-02-26 18:15:04 +02:00
|
|
|
if (!isEqual(newEntityId, prevEntityId)) {
|
2019-10-11 19:22:03 +03:00
|
|
|
changedAliasIds.push(aliasId);
|
|
|
|
|
this.setAliasUnresolved(aliasId);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (changedAliasIds.length) {
|
|
|
|
|
this.entityAliasesChangedSubject.next(changedAliasIds);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private setAliasUnresolved(aliasId: string) {
|
|
|
|
|
delete this.resolvedAliases[aliasId];
|
|
|
|
|
delete this.resolvedAliasesObservable[aliasId];
|
|
|
|
|
delete this.resolvedAliasesToStateEntities[aliasId];
|
2019-09-19 20:10:52 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
getEntityAliases(): EntityAliases {
|
|
|
|
|
return this.entityAliases;
|
|
|
|
|
}
|
|
|
|
|
|
2019-10-11 19:22:03 +03:00
|
|
|
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<AliasInfo> {
|
|
|
|
|
const aliasInfo = this.resolvedAliases[aliasId];
|
|
|
|
|
if (aliasInfo) {
|
|
|
|
|
return of(aliasInfo);
|
|
|
|
|
} else if (this.resolvedAliasesObservable[aliasId]) {
|
|
|
|
|
return this.resolvedAliasesObservable[aliasId];
|
|
|
|
|
} else {
|
|
|
|
|
const resolvedAliasSubject = new ReplaySubject<AliasInfo>();
|
|
|
|
|
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);
|
2020-01-20 15:14:08 +02:00
|
|
|
const res = this.resolvedAliasesObservable[aliasId];
|
2019-10-11 19:22:03 +03:00
|
|
|
delete this.resolvedAliasesObservable[aliasId];
|
2020-01-20 15:14:08 +02:00
|
|
|
return res;
|
2019-10-11 19:22:03 +03:00
|
|
|
}
|
|
|
|
|
return this.resolvedAliasesObservable[aliasId];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private resolveDatasource(datasource: Datasource, isSingle?: boolean): Observable<Array<Datasource>> {
|
|
|
|
|
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<Datasource> = [];
|
|
|
|
|
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<Datasource> {
|
|
|
|
|
return this.resolveDatasource(alarmSource, true).pipe(
|
|
|
|
|
map((datasources) => {
|
|
|
|
|
const datasource = datasources[0];
|
|
|
|
|
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<Datasource>): Observable<Array<Datasource>> {
|
|
|
|
|
const newDatasources = deepClone(datasources);
|
|
|
|
|
const observables = new Array<Observable<Array<Datasource>>>();
|
|
|
|
|
newDatasources.forEach((datasource) => {
|
|
|
|
|
observables.push(this.resolveDatasource(datasource));
|
|
|
|
|
});
|
|
|
|
|
return forkJoin(observables).pipe(
|
|
|
|
|
map((arrayOfDatasources) => {
|
|
|
|
|
const result = new Array<Datasource>();
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
|
2019-09-19 20:10:52 +03:00
|
|
|
getInstantAliasInfo(aliasId: string): AliasInfo {
|
|
|
|
|
return this.resolvedAliases[aliasId];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
updateCurrentAliasEntity(aliasId: string, currentEntity: EntityInfo) {
|
|
|
|
|
const aliasInfo = this.resolvedAliases[aliasId];
|
|
|
|
|
if (aliasInfo) {
|
|
|
|
|
const prevCurrentEntity = aliasInfo.currentEntity;
|
2020-02-26 18:15:04 +02:00
|
|
|
if (!isEqual(currentEntity, prevCurrentEntity)) {
|
2019-09-19 20:10:52 +03:00
|
|
|
aliasInfo.currentEntity = currentEntity;
|
|
|
|
|
this.entityAliasesChangedSubject.next([aliasId]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-09-10 15:12:10 +03:00
|
|
|
}
|