UI: Refactor entities hierarchy widget to use new WS data query API.

This commit is contained in:
Igor Kulikov 2020-07-06 18:00:10 +03:00
parent 2eb7949d57
commit 92c4d00fbb
5 changed files with 141 additions and 145 deletions

File diff suppressed because one or more lines are too long

View File

@ -8997,10 +8997,10 @@
"integrity": "sha512-4O3GWAYJaauMCILm07weko2rHA8a4kjn7+8Lg4s1d7SxwS/3IpkVD/GljbRrIJ1c1W/XGJ3GbuK7RyYZEJChhw=="
},
"ngx-flowchart": {
"version": "git://github.com/thingsboard/ngx-flowchart.git#a4157b0eef2eb3646ef920447c7b06b39d54f87f",
"version": "git://github.com/thingsboard/ngx-flowchart.git#7a02f4748b5e7821a883c903107af5f20415d026",
"from": "git://github.com/thingsboard/ngx-flowchart.git#master",
"requires": {
"tslib": "^1.10.0"
"tslib": "^1.13.0"
},
"dependencies": {
"tslib": {

View File

@ -20,7 +20,7 @@ import { 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 { AliasFilterType, EntityAliases, SingleEntityFilter } from '@shared/models/alias.models';
import { EntityInfo } from '@shared/models/entity.models';
import { map, mergeMap } from 'rxjs/operators';
import {
@ -282,6 +282,15 @@ export class AliasController implements IAliasController {
}
})
);
} else if (newDatasource.entityId && !newDatasource.entityFilter) {
newDatasource.entityFilter = {
singleEntity: {
id: newDatasource.entityId,
entityType: newDatasource.entityType,
},
type: AliasFilterType.singleEntity
} as SingleEntityFilter;
return of(newDatasource);
} else {
newDatasource.aliasName = newDatasource.entityName;
newDatasource.name = newDatasource.entityName;

View File

@ -23,19 +23,18 @@ import { DatasourceData, DatasourceType, WidgetConfig, widgetType } from '@share
import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
import { UtilsService } from '@core/services/utils.service';
import cssjs from '@core/css/css';
import { forkJoin, fromEvent, Observable, of } from 'rxjs';
import { catchError, debounceTime, distinctUntilChanged, map, mergeMap, tap } from 'rxjs/operators';
import { fromEvent } from 'rxjs';
import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
import { constructTableCssString } from '@home/components/widget/lib/table-widget.models';
import { Overlay } from '@angular/cdk/overlay';
import {
LoadNodesCallback,
NavTreeEditCallbacks,
NodesCallback,
NodeSearchCallback,
NodeSelectedCallback,
NodesInsertedCallback
} from '@shared/components/nav-tree.component';
import { BaseData } from '@shared/models/base-data';
import { EntityId } from '@shared/models/id/entity-id';
import { EntityType } from '@shared/models/entity-type.models';
import { deepClone, hashCode } from '@core/utils';
import {
@ -58,10 +57,9 @@ import {
NodesSortFunction,
NodeTextFunction
} from '@home/components/widget/lib/entities-hierarchy-widget.models';
import { EntityService } from '@core/http/entity.service';
import { EntityRelationsQuery, EntitySearchDirection } from '@shared/models/relation.models';
import { EntityRelationService } from '@core/http/entity-relation.service';
import { ActionNotificationShow } from '@core/notification/notification.actions';
import { EntityRelationsQuery } from '@shared/models/relation.models';
import { AliasFilterType, RelationsQueryFilter } from '@shared/models/alias.models';
import { EntityFilter } from '@shared/models/query/query.models';
@Component({
selector: 'tb-entities-hierarchy-widget',
@ -86,6 +84,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
private widgetConfig: WidgetConfig;
private subscription: IWidgetSubscription;
private datasources: Array<HierarchyNodeDatasource>;
private data: Array<Array<DatasourceData>>;
private nodesMap: {[nodeId: string]: HierarchyNavTreeNode} = {};
private pendingUpdateNodeTasks: {[nodeId: string]: () => void} = {};
@ -108,14 +107,11 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
}
};
constructor(protected store: Store<AppState>,
private elementRef: ElementRef,
private overlay: Overlay,
private viewContainerRef: ViewContainerRef,
private utils: UtilsService,
private entityService: EntityService,
private entityRelationService: EntityRelationService) {
private utils: UtilsService) {
super(store);
}
@ -125,6 +121,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
this.widgetConfig = this.ctx.widgetConfig;
this.subscription = this.ctx.defaultSubscription;
this.datasources = this.subscription.datasources as Array<HierarchyNodeDatasource>;
this.data = this.subscription.dataPages[0].data;
this.initializeConfig();
this.ctx.updateWidgetParams();
}
@ -254,33 +251,15 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
public loadNodes: LoadNodesCallback = (node, cb) => {
if (node.id === '#') {
const tasks: Observable<HierarchyNavTreeNode>[] = [];
this.datasources.forEach((datasource) => {
tasks.push(this.datasourceToNode(datasource));
});
forkJoin(tasks).subscribe((nodes) => {
cb(this.prepareNodes(nodes));
this.updateNodeData(this.subscription.data);
const childNodes: HierarchyNavTreeNode[] = [];
this.datasources.forEach((childDatasource, index) => {
childNodes.push(this.datasourceToNode(childDatasource as HierarchyNodeDatasource, this.data[index]));
});
cb(this.prepareNodes(childNodes));
} else {
if (node.data && node.data.nodeCtx.entity && node.data.nodeCtx.entity.id && node.data.nodeCtx.entity.id.entityType !== 'function') {
const relationQuery = this.prepareNodeRelationQuery(node.data.nodeCtx);
this.entityRelationService.findByQuery(relationQuery, {ignoreErrors: true, ignoreLoading: true}).subscribe(
(entityRelations) => {
if (entityRelations.length) {
const tasks: Observable<HierarchyNavTreeNode>[] = [];
entityRelations.forEach((relation) => {
const targetId = relationQuery.parameters.direction === EntitySearchDirection.FROM ? relation.to : relation.from;
tasks.push(this.entityIdToNode(targetId.entityType as EntityType, targetId.id, node.data.datasource, node.data.nodeCtx));
});
forkJoin(tasks).subscribe((nodes) => {
cb(this.prepareNodes(nodes));
});
} else {
cb([]);
}
},
(error) => {
this.loadChildren(node, node.data.datasource, cb);
/* (error) => { // TODO:
let errorText = 'Failed to get relations!';
if (error && error.status === 400) {
errorText = 'Invalid relations query returned by \'Node relations query function\'! Please check widget configuration!';
@ -288,6 +267,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
this.showError(errorText);
}
);
*/
} else {
cb([]);
}
@ -313,7 +293,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
}
}
public onNodesInserted: NodesInsertedCallback = (nodes, parent) => {
public onNodesInserted: NodesInsertedCallback = (nodes) => {
if (nodes) {
nodes.forEach((nodeId) => {
const task = this.pendingUpdateNodeTasks[nodeId];
@ -355,17 +335,6 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
}
}
private showError(errorText: string) {
this.store.dispatch(new ActionNotificationShow(
{
message: errorText,
type: 'error',
target: this.toastTargetId,
verticalPosition: 'bottom',
horizontalPosition: 'left'
}));
}
private prepareNodes(nodes: HierarchyNavTreeNode[]): HierarchyNavTreeNode[] {
nodes = nodes.filter((node) => node !== null);
nodes.sort((node1, node2) => this.nodesSortFunction(node1.data.nodeCtx, node2.data.nodeCtx));
@ -399,10 +368,9 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
}
}
private datasourceToNode(datasource: HierarchyNodeDatasource, parentNodeCtx?: HierarchyNodeContext): Observable<HierarchyNavTreeNode> {
return this.resolveEntity(datasource).pipe(
map(entity => {
if (entity !== null) {
private datasourceToNode(datasource: HierarchyNodeDatasource,
data: DatasourceData[],
parentNodeCtx?: HierarchyNodeContext): HierarchyNavTreeNode {
const node: HierarchyNavTreeNode = {
id: (++this.nodeIdCounter) + ''
};
@ -411,9 +379,24 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
node.icon = false;
const nodeCtx: HierarchyNodeContext = {
parentNodeCtx,
entity,
entity: {
id: {
id: datasource.entityId,
entityType: datasource.entityType
},
name: datasource.entityName,
label: datasource.entityLabel ? datasource.entityLabel : datasource.entityName
},
data: {}
};
datasource.dataKeys.forEach((dataKey, index) => {
const keyData = data[index].data;
if (keyData && keyData.length && keyData[0].length > 1) {
nodeCtx.data[dataKey.label] = keyData[0][1];
} else {
nodeCtx.data[dataKey.label] = '';
}
});
nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1;
node.data = {
datasource,
@ -426,59 +409,47 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
node.text = this.prepareNodeText(node);
node.children = this.nodeHasChildrenFunction(node.data.nodeCtx);
return node;
} else {
return null;
}
})
);
}
private entityIdToNode(entityType: EntityType, entityId: string,
parentDatasource: HierarchyNodeDatasource,
parentNodeCtx: HierarchyNodeContext): Observable<HierarchyNavTreeNode> {
const datasource = {
dataKeys: parentDatasource.dataKeys,
private loadChildren(parentNode: HierarchyNavTreeNode, datasource: HierarchyNodeDatasource, childrenNodesLoadCb: NodesCallback) {
const nodeCtx = parentNode.data.nodeCtx;
nodeCtx.childrenNodesLoaded = false;
const entityFilter = this.prepareNodeRelationsQueryFilter(nodeCtx);
const childrenDatasource = {
dataKeys: datasource.dataKeys,
type: DatasourceType.entity,
entityType,
entityId
entityFilter
} as HierarchyNodeDatasource;
return this.datasourceToNode(datasource, parentNodeCtx).pipe(
mergeMap((node) => {
if (node != null) {
const subscriptionOptions: WidgetSubscriptionOptions = {
type: widgetType.latest,
datasources: [datasource],
datasources: [childrenDatasource],
callbacks: {
onDataUpdated: subscription => {
this.updateNodeData(subscription.data);
}
}
};
return this.ctx.subscriptionApi.
createSubscription(subscriptionOptions, true).pipe(
map(() => node));
} else {
return of(node);
}
})
);
}
private resolveEntity(datasource: HierarchyNodeDatasource): Observable<BaseData<EntityId>> {
if (datasource.type === DatasourceType.function) {
const entity = {
id: {
entityType: 'function'
onSubscriptionMessage: (subscription, message) => {
this.ctx.showToast(message.severity, message.message, undefined,
'bottom', 'left', this.toastTargetId);
},
name: datasource.name
};
return of(entity as BaseData<EntityId>);
onInitialPageDataChanged: (subscription) => {
this.ctx.subscriptionApi.removeSubscription(subscription.id);
this.nodeEditCallbacks.refreshNode(parentNode.id);
},
onDataUpdated: subscription => {
if (nodeCtx.childrenNodesLoaded) {
this.updateNodeData(subscription.data);
} else {
return this.entityService.getEntity(datasource.entityType, datasource.entityId, {ignoreLoading: true}).pipe(
catchError(err => of(null))
);
const datasourcesPageData = subscription.datasourcePages[0];
const dataPageData = subscription.dataPages[0];
const childNodes: HierarchyNavTreeNode[] = [];
datasourcesPageData.data.forEach((childDatasource, index) => {
childNodes.push(this.datasourceToNode(childDatasource as HierarchyNodeDatasource, dataPageData.data[index]));
});
nodeCtx.childrenNodesLoaded = true;
childrenNodesLoadCb(this.prepareNodes(childNodes));
}
}
}
};
this.ctx.subscriptionApi.createSubscription(subscriptionOptions, true);
}
private prepareNodeRelationQuery(nodeCtx: HierarchyNodeContext): EntityRelationsQuery {
let relationQuery = this.nodeRelationQueryFunction(nodeCtx);
@ -487,4 +458,19 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
}
return relationQuery as EntityRelationsQuery;
}
private prepareNodeRelationsQueryFilter(nodeCtx: HierarchyNodeContext): EntityFilter {
const relationQuery = this.prepareNodeRelationQuery(nodeCtx);
return {
rootEntity: {
id: relationQuery.parameters.rootId,
entityType: relationQuery.parameters.rootType
},
direction: relationQuery.parameters.direction,
filters: relationQuery.filters,
maxLevel: relationQuery.parameters.maxLevel,
fetchLastLevelOnly: relationQuery.parameters.fetchLastLevelOnly,
type: AliasFilterType.relationsQuery
} as RelationsQueryFilter;
}
}

View File

@ -16,7 +16,7 @@
import { BaseData } from '@shared/models/base-data';
import { EntityId } from '@shared/models/id/entity-id';
import { NavTreeNode } from '@shared/components/nav-tree.component';
import { NavTreeNode, NodesCallback } from '@shared/components/nav-tree.component';
import { Datasource } from '@shared/models/widget.models';
import { isDefined, isUndefined } from '@core/utils';
import { EntityRelationsQuery, EntitySearchDirection, RelationTypeGroup } from '@shared/models/relation.models';
@ -35,6 +35,7 @@ export interface EntitiesHierarchyWidgetSettings {
export interface HierarchyNodeContext {
parentNodeCtx?: HierarchyNodeContext;
entity: BaseData<EntityId>;
childrenNodesLoaded?: boolean;
level?: number;
data: {[key: string]: any};
}