UI: Refactor entities hierarchy widget to use new WS data query API.
This commit is contained in:
parent
2eb7949d57
commit
92c4d00fbb
File diff suppressed because one or more lines are too long
4
ui-ngx/package-lock.json
generated
4
ui-ngx/package-lock.json
generated
@ -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": {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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};
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user