Commit 0ab5f30be4cf10bfb7db5e21dad7c832aa872467

Authored by deaflynx
1 parent 1b276322

Edge overview widget ready

1 1 {
2 2 "widgetsBundle": {
3 3 "alias": "edges_widgets",
4   - "title": "Edge Instances widgets",
  4 + "title": "Edge widgets",
5 5 "image": null
6 6 },
7 7 "widgetTypes": [
8 8 {
9 9 "alias": "edges_overview",
10   - "name": "Edge Instances Overview",
  10 + "name": "Edges Quick Overview",
11 11 "descriptor": {
12   - "type": "static",
  12 + "type": "latest",
13 13 "sizeX": 7.5,
14 14 "sizeY": 5,
15 15 "resources": [],
... ... @@ -18,7 +18,7 @@
18 18 "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.edgesOverviewWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n dataKeysOptional: true\n };\n}\n\nself.actionSources = function() {\n return {\n 'nodeSelected': {\n name: 'widget-action.node-selected',\n multiple: false\n }\n };\n}\n\nself.onDestroy = function() {\n}\n",
19 19 "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EdgesOverviewSettings\",\n \"properties\": {\n \"nodeRelationQueryFunction\": {\n \"title\": \"Node relations query function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeHasChildrenFunction\": {\n \"title\": \"Node has children function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeOpenedFunction\": {\n \"title\": \"Default node opened function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeDisabledFunction\": {\n \"title\": \"Node disabled function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeIconFunction\": {\n \"title\": \"Node icon function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodeTextFunction\": {\n \"title\": \"Node text function: f(nodeCtx)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"nodesSortFunction\": {\n \"title\": \"Nodes sort function: f(nodeCtx1, nodeCtx2)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n {\n \"key\": \"nodeRelationQueryFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeHasChildrenFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeOpenedFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeDisabledFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeIconFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodeTextFunction\",\n \"type\": \"javascript\"\n },\n {\n \"key\": \"nodesSortFunction\",\n \"type\": \"javascript\"\n }\n ]\n}",
20 20 "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {},\n \"required\": []\n },\n \"form\": []\n}",
21   - "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"nodeRelationQueryFunction\":\"/**\\n\\n// Function should return relations query object for current node used to fetch entity children.\\n// Function can return 'default' string value. In this case default relations query will be used.\\n\\n// The following example code will construct simple relations query that will fetch relations of type 'Contains'\\n// from the current entity.\\n\\nvar entity = nodeCtx.entity;\\nvar query = {\\n parameters: {\\n rootId: entity.id.id,\\n rootType: entity.id.entityType,\\n direction: \\\"FROM\\\",\\n maxLevel: 1\\n },\\n filters: [{\\n relationType: \\\"Contains\\\",\\n entityTypes: []\\n }]\\n};\\nreturn query;\\n\\n**/\\n\",\"nodeHasChildrenFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node has children (whether it can be expanded).\\n\\n// The following example code will restrict entities hierarchy expansion up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n// The next example code will restrict entities expansion according to the value of example 'nodeHasChildren' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeHasChildren') && data['nodeHasChildren'] !== null) {\\n return data['nodeHasChildren'] === 'true';\\n} else {\\n return true;\\n}\\n \\n**/\\n \",\"nodeTextFunction\":\"/**\\n\\n// Function should return text (can be HTML code) for the current node.\\n\\n// The following example code will generate node text consisting of entity name and temperature if temperature value is present in entity attributes/timeseries.\\n\\nvar data = nodeCtx.data;\\nvar entity = nodeCtx.entity;\\nvar text = entity.name;\\nif (data.hasOwnProperty('temperature') && data['temperature'] !== null) {\\n text += \\\" <b>\\\"+ data['temperature'] +\\\" °C</b>\\\";\\n}\\nreturn text;\\n\\n**/\",\"nodeIconFunction\":\"/** \\n\\n// Function should return node icon info object.\\n// Resulting object should contain either 'materialIcon' or 'iconUrl' property. \\n// Where:\\n - 'materialIcon' - name of the material icon to be used from the Material Icons Library (https://material.io/tools/icons);\\n - 'iconUrl' - url of the external image to be used as node icon.\\n// Function can return 'default' string value. In this case default icons according to entity type will be used.\\n\\n// The following example code shows how to use external image for devices which name starts with 'Test' and use \\n// default icons for the rest of entities.\\n\\nvar entity = nodeCtx.entity;\\nif (entity.id.entityType === 'DEVICE' && entity.name.startsWith('Test')) {\\n return {iconUrl: 'https://avatars1.githubusercontent.com/u/14793288?v=4&s=117'};\\n} else {\\n return 'default';\\n}\\n \\n**/\",\"nodeDisabledFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be disabled (not selectable).\\n\\n// The following example code will disable current node according to the value of example 'nodeDisabled' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeDisabled') && data['nodeDisabled'] !== null) {\\n return data['nodeDisabled'] === 'true';\\n} else {\\n return false;\\n}\\n \\n**/\\n\",\"nodesSortFunction\":\"/**\\n\\n// This function is used to sort nodes of the same level. Function should compare two nodes and return \\n// integer value: \\n// - less than 0 - sort nodeCtx1 to an index lower than nodeCtx2\\n// - 0 - leave nodeCtx1 and nodeCtx2 unchanged with respect to each other\\n// - greater than 0 - sort nodeCtx2 to an index lower than nodeCtx1\\n\\n// The following example code will sort entities first by entity type in alphabetical order then\\n// by entity name in alphabetical order.\\n\\nvar result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);\\nif (result === 0) {\\n result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);\\n}\\nreturn result;\\n \\n**/\",\"nodeOpenedFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be opened (expanded) when it first loaded.\\n\\n// The following example code will open by default nodes up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n**/\\n \"},\"title\":\"Edge Instances Overview\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"widgetStyle\":{},\"actions\":{}}"
  21 + "defaultConfig": "{\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":86400000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"4px\",\"settings\":{\"nodeRelationQueryFunction\":\"/**\\n\\n// Function should return relations query object for current node used to fetch entity children.\\n// Function can return 'default' string value. In this case default relations query will be used.\\n\\n// The following example code will construct simple relations query that will fetch relations of type 'Contains'\\n// from the current entity.\\n\\nvar entity = nodeCtx.entity;\\nvar query = {\\n parameters: {\\n rootId: entity.id.id,\\n rootType: entity.id.entityType,\\n direction: \\\"FROM\\\",\\n maxLevel: 1\\n },\\n filters: [{\\n relationType: \\\"Contains\\\",\\n entityTypes: []\\n }]\\n};\\nreturn query;\\n\\n**/\\n\",\"nodeHasChildrenFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node has children (whether it can be expanded).\\n\\n// The following example code will restrict entities hierarchy expansion up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n// The next example code will restrict entities expansion according to the value of example 'nodeHasChildren' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeHasChildren') && data['nodeHasChildren'] !== null) {\\n return data['nodeHasChildren'] === 'true';\\n} else {\\n return true;\\n}\\n \\n**/\\n \",\"nodeTextFunction\":\"/**\\n\\n// Function should return text (can be HTML code) for the current node.\\n\\n// The following example code will generate node text consisting of entity name and temperature if temperature value is present in entity attributes/timeseries.\\n\\nvar data = nodeCtx.data;\\nvar entity = nodeCtx.entity;\\nvar text = entity.name;\\nif (data.hasOwnProperty('temperature') && data['temperature'] !== null) {\\n text += \\\" <b>\\\"+ data['temperature'] +\\\" °C</b>\\\";\\n}\\nreturn text;\\n\\n**/\",\"nodeIconFunction\":\"/** \\n\\n// Function should return node icon info object.\\n// Resulting object should contain either 'materialIcon' or 'iconUrl' property. \\n// Where:\\n - 'materialIcon' - name of the material icon to be used from the Material Icons Library (https://material.io/tools/icons);\\n - 'iconUrl' - url of the external image to be used as node icon.\\n// Function can return 'default' string value. In this case default icons according to entity type will be used.\\n\\n// The following example code shows how to use external image for devices which name starts with 'Test' and use \\n// default icons for the rest of entities.\\n\\nvar entity = nodeCtx.entity;\\nif (entity.id.entityType === 'DEVICE' && entity.name.startsWith('Test')) {\\n return {iconUrl: 'https://avatars1.githubusercontent.com/u/14793288?v=4&s=117'};\\n} else {\\n return 'default';\\n}\\n \\n**/\",\"nodeDisabledFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be disabled (not selectable).\\n\\n// The following example code will disable current node according to the value of example 'nodeDisabled' attribute.\\n\\nvar data = nodeCtx.data;\\nif (data.hasOwnProperty('nodeDisabled') && data['nodeDisabled'] !== null) {\\n return data['nodeDisabled'] === 'true';\\n} else {\\n return false;\\n}\\n \\n**/\\n\",\"nodesSortFunction\":\"/**\\n\\n// This function is used to sort nodes of the same level. Function should compare two nodes and return \\n// integer value: \\n// - less than 0 - sort nodeCtx1 to an index lower than nodeCtx2\\n// - 0 - leave nodeCtx1 and nodeCtx2 unchanged with respect to each other\\n// - greater than 0 - sort nodeCtx2 to an index lower than nodeCtx1\\n\\n// The following example code will sort entities first by entity type in alphabetical order then\\n// by entity name in alphabetical order.\\n\\nvar result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);\\nif (result === 0) {\\n result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);\\n}\\nreturn result;\\n \\n**/\",\"nodeOpenedFunction\":\"/**\\n\\n// Function should return boolean value indicating whether current node should be opened (expanded) when it first loaded.\\n\\n// The following example code will open by default nodes up to third level.\\n\\nreturn nodeCtx.level <= 2;\\n\\n**/\\n \"},\"title\":\"Edge Quick Overview Widget\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400,\"padding\":\"5px 10px 5px 10px\"},\"useDashboardTimewindow\":false,\"showLegend\":false,\"datasources\":[{\"type\":\"function\",\"name\":\"Simulated\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Sin\",\"color\":\"#2196f3\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.472295003170325,\"funcBody\":\"return Math.round(1000*Math.sin(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Cos\",\"color\":\"#4caf50\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.8926244886945558,\"funcBody\":\"return Math.round(1000*Math.cos(time/5000));\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#f44336\",\"settings\":{\"columnWidth\":\"0px\",\"useCellStyleFunction\":false,\"cellStyleFunction\":\"\",\"useCellContentFunction\":false,\"cellContentFunction\":\"\"},\"_hash\":0.6401141393938932,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"widgetStyle\":{},\"actions\":{}}"
22 22 }
23 23 }
24 24 ]
... ...
... ... @@ -37,6 +37,9 @@
37 37 </button>
38 38 </div>
39 39 </mat-toolbar>
  40 + <div *ngIf="customerTitle" fxLayout="row" class="customer-info">
  41 + <span>{{ customerTitle }}</span>
  42 + </div>
40 43 <div fxFlex class="tb-entities-nav-tree-panel">
41 44 <tb-nav-tree
42 45 [loadNodes]="loadNodes"
... ...
... ... @@ -40,6 +40,10 @@
40 40 }
41 41 }
42 42
  43 + .customer-info {
  44 + padding: 15px;
  45 + }
  46 +
43 47 .tb-entities-nav-tree-panel {
44 48 overflow-x: auto;
45 49 overflow-y: auto;
... ...
... ... @@ -14,7 +14,7 @@
14 14 /// limitations under the License.
15 15 ///
16 16
17   -import { AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
  17 +import { ChangeDetectorRef, AfterViewInit, Component, ElementRef, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
18 18 import { PageComponent } from '@shared/components/page.component';
19 19 import { Store } from '@ngrx/store';
20 20 import { AppState } from '@core/core.state';
... ... @@ -23,8 +23,8 @@ import { DatasourceData, DatasourceType, WidgetConfig, widgetType } from '@share
23 23 import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models';
24 24 import { UtilsService } from '@core/services/utils.service';
25 25 import cssjs from '@core/css/css';
26   -import { fromEvent } from 'rxjs';
27   -import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators';
  26 +import { fromEvent, Observable } from 'rxjs';
  27 +import { debounceTime, distinctUntilChanged, map, mergeMap, tap } from 'rxjs/operators';
28 28 import { constructTableCssString } from '@home/components/widget/lib/table-widget.models';
29 29 import { Overlay } from '@angular/cdk/overlay';
30 30 import {
... ... @@ -71,6 +71,8 @@ import { TranslateService } from "@ngx-translate/core";
71 71 import { Direction, SortOrder } from "@shared/models/page/sort-order";
72 72 import { PageLink } from "@shared/models/page/page-link";
73 73 import { Edge, EdgeInfo } from "@shared/models/edge.models";
  74 +import { BaseData } from "@shared/models/base-data";
  75 +import { EntityId } from "@shared/models/id/entity-id";
74 76
75 77 @Component({
76 78 selector: 'tb-edges-overview-widget',
... ... @@ -88,12 +90,15 @@ export class EdgesOverviewWidgetComponent extends PageComponent implements OnIni
88 90
89 91 public textSearchMode = false;
90 92 public textSearch = null;
  93 + public customerTitle: string = null;
91 94
92 95 public nodeEditCallbacks: NavTreeEditCallbacks = {};
93 96
94 97 private settings: EdgesOverviewWidgetSettings;
95 98 private widgetConfig: WidgetConfig;
96 99 private subscription: IWidgetSubscription;
  100 + private datasources: Array<HierarchyNodeDatasource>;
  101 + private data: Array<Array<DatasourceData>>;
97 102
98 103 private nodesMap: {[nodeId: string]: HierarchyNavTreeNode} = {};
99 104 private pendingUpdateNodeTasks: {[nodeId: string]: () => void} = {};
... ... @@ -113,7 +118,7 @@ export class EdgesOverviewWidgetComponent extends PageComponent implements OnIni
113 118
114 119 private searchAction: WidgetAction = {
115 120 name: 'action.search',
116   - show: true,
  121 + show: false,
117 122 icon: 'search',
118 123 onAction: () => {
119 124 this.enterFilterMode();
... ... @@ -127,7 +132,8 @@ export class EdgesOverviewWidgetComponent extends PageComponent implements OnIni
127 132 private translateService: TranslateService,
128 133 private overlay: Overlay,
129 134 private viewContainerRef: ViewContainerRef,
130   - private utils: UtilsService) {
  135 + private utils: UtilsService,
  136 + private cd: ChangeDetectorRef) {
131 137 super(store);
132 138 }
133 139
... ... @@ -136,6 +142,10 @@ export class EdgesOverviewWidgetComponent extends PageComponent implements OnIni
136 142 this.settings = this.ctx.settings;
137 143 this.widgetConfig = this.ctx.widgetConfig;
138 144 this.subscription = this.ctx.defaultSubscription;
  145 + this.datasources = this.subscription.datasources as Array<HierarchyNodeDatasource>;
  146 + this.data = this.subscription.dataPages[0].data;
  147 + this.ctx.widgetTitle = this.datasources[0].entity.name;
  148 + this.getCustomerTitle(this.datasources[0].entity.id.id);
139 149 this.initializeConfig();
140 150 this.ctx.updateWidgetParams();
141 151 }
... ... @@ -265,16 +275,10 @@ export class EdgesOverviewWidgetComponent extends PageComponent implements OnIni
265 275
266 276 public loadNodes: LoadNodesCallback = (node, cb) => {
267 277 if (node.id === '#') {
268   - const sortOrder: SortOrder = { property: 'name', direction: Direction.ASC };
269   - const pageLink = new PageLink(100, 0, null, sortOrder);
270   - this.edgeService.getTenantEdgeInfos(pageLink).subscribe(
271   - (edges) => {
272   - cb(this.edgesToNodes(node.id, edges.data))
273   - });
274   - } else if (node.data.type === 'edge') {
275   - const edge = node.data.entity;
276   - cb(this.loadNodesForEdge(node.id, edge));
277   - } else if (node.data.type === 'edgeGroups') {
  278 + const edge: BaseData<EntityId> = this.datasources[0].entity;
  279 + cb(this.loadNodesForEdge(edge.id.id, edge));
  280 + }
  281 + else if (node.data.type === 'edgeGroups') {
278 282 const pageLink = new PageLink(100);
279 283 this.entityService.getAssignedToEdgeEntitiesByType(node, pageLink).subscribe(
280 284 (entities) => {
... ... @@ -288,7 +292,7 @@ export class EdgesOverviewWidgetComponent extends PageComponent implements OnIni
288 292 }
289 293 }
290 294
291   - private loadNodesForEdge(parentNodeId: string, edge: EdgeInfo): EdgeOverviewNode[] {
  295 + private loadNodesForEdge(parentNodeId: string, edge: any): EdgeOverviewNode[] {
292 296 const nodes: EdgeOverviewNode[] = [];
293 297 const nodesMap = {};
294 298 this.edgeGroupsNodesMap[parentNodeId] = nodesMap;
... ... @@ -408,12 +412,6 @@ export class EdgesOverviewWidgetComponent extends PageComponent implements OnIni
408 412 }
409 413 }
410 414
411   - private prepareNodes(nodes: HierarchyNavTreeNode[]): HierarchyNavTreeNode[] {
412   - nodes = nodes.filter((node) => node !== null);
413   - nodes.sort((node1, node2) => this.nodesSortFunction(node1.data.nodeCtx, node2.data.nodeCtx));
414   - return nodes;
415   - }
416   -
417 415 private prepareNodeText(node: HierarchyNavTreeNode): string {
418 416 const nodeIcon = this.prepareNodeIcon(node.data.nodeCtx);
419 417 const nodeText = this.nodeTextFunction(node.data.nodeCtx);
... ... @@ -441,110 +439,15 @@ export class EdgesOverviewWidgetComponent extends PageComponent implements OnIni
441 439 }
442 440 }
443 441
444   - private datasourceToNode(datasource: HierarchyNodeDatasource,
445   - data: DatasourceData[],
446   - parentNodeCtx?: HierarchyNodeContext): HierarchyNavTreeNode {
447   - const node: HierarchyNavTreeNode = {
448   - id: (++this.nodeIdCounter) + ''
449   - };
450   - this.nodesMap[node.id] = node;
451   - datasource.nodeId = node.id;
452   - node.icon = false;
453   - const nodeCtx: HierarchyNodeContext = {
454   - parentNodeCtx,
455   - entity: {
456   - id: {
457   - id: datasource.entityId,
458   - entityType: datasource.entityType
459   - },
460   - name: datasource.entityName,
461   - label: datasource.entityLabel ? datasource.entityLabel : datasource.entityName
462   - },
463   - data: {}
464   - };
465   - datasource.dataKeys.forEach((dataKey, index) => {
466   - const keyData = data[index].data;
467   - if (keyData && keyData.length && keyData[0].length > 1) {
468   - nodeCtx.data[dataKey.label] = keyData[0][1];
469   - } else {
470   - nodeCtx.data[dataKey.label] = '';
471   - }
472   - });
473   - nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1;
474   - node.data = {
475   - datasource,
476   - nodeCtx
477   - };
478   - node.state = {
479   - disabled: this.nodeDisabledFunction(node.data.nodeCtx),
480   - opened: this.nodeOpenedFunction(node.data.nodeCtx)
481   - };
482   - node.text = this.prepareNodeText(node);
483   - node.children = this.nodeHasChildrenFunction(node.data.nodeCtx);
484   - return node;
485   - }
486   -
487   - private loadChildren(parentNode: HierarchyNavTreeNode, datasource: HierarchyNodeDatasource, childrenNodesLoadCb: NodesCallback) {
488   - const nodeCtx = parentNode.data.nodeCtx;
489   - nodeCtx.childrenNodesLoaded = false;
490   - const entityFilter = this.prepareNodeRelationsQueryFilter(nodeCtx);
491   - const childrenDatasource = {
492   - dataKeys: datasource.dataKeys,
493   - type: DatasourceType.entity,
494   - filterId: datasource.filterId,
495   - entityFilter
496   - } as HierarchyNodeDatasource;
497   - const subscriptionOptions: WidgetSubscriptionOptions = {
498   - type: widgetType.latest,
499   - datasources: [childrenDatasource],
500   - callbacks: {
501   - onSubscriptionMessage: (subscription, message) => {
502   - this.ctx.showToast(message.severity, message.message, undefined,
503   - 'bottom', 'left', this.toastTargetId);
504   - },
505   - onInitialPageDataChanged: (subscription) => {
506   - this.ctx.subscriptionApi.removeSubscription(subscription.id);
507   - this.nodeEditCallbacks.refreshNode(parentNode.id);
508   - },
509   - onDataUpdated: subscription => {
510   - if (nodeCtx.childrenNodesLoaded) {
511   - this.updateNodeData(subscription.data);
512   - } else {
513   - const datasourcesPageData = subscription.datasourcePages[0];
514   - const dataPageData = subscription.dataPages[0];
515   - const childNodes: HierarchyNavTreeNode[] = [];
516   - datasourcesPageData.data.forEach((childDatasource, index) => {
517   - childNodes.push(this.datasourceToNode(childDatasource as HierarchyNodeDatasource, dataPageData.data[index]));
518   - });
519   - nodeCtx.childrenNodesLoaded = true;
520   - childrenNodesLoadCb(this.prepareNodes(childNodes));
521   - }
  442 + private getCustomerTitle(edgeId) {
  443 + this.edgeService.getEdgeInfo(edgeId).subscribe(
  444 + (edge) => {
  445 + if (edge.customerTitle) {
  446 + this.customerTitle = this.translateService.instant('edge.assigned-to-customer') + ': ' + edge.customerTitle;
  447 + } else {
  448 + this.customerTitle = null;
522 449 }
523   - }
524   - };
525   - this.ctx.subscriptionApi.createSubscription(subscriptionOptions, true);
526   - }
527   -
528   - private prepareNodeRelationQuery(nodeCtx: HierarchyNodeContext): EntityRelationsQuery {
529   - let relationQuery = this.nodeRelationQueryFunction(nodeCtx);
530   - if (relationQuery && relationQuery === 'default') {
531   - relationQuery = defaultNodeRelationQueryFunction(nodeCtx);
532   - }
533   - return relationQuery as EntityRelationsQuery;
534   - }
535   -
536   - private prepareNodeRelationsQueryFilter(nodeCtx: HierarchyNodeContext): EntityFilter {
537   - const relationQuery = this.prepareNodeRelationQuery(nodeCtx);
538   - return {
539   - rootEntity: {
540   - id: relationQuery.parameters.rootId,
541   - entityType: relationQuery.parameters.rootType
542   - },
543   - direction: relationQuery.parameters.direction,
544   - filters: relationQuery.filters,
545   - maxLevel: relationQuery.parameters.maxLevel,
546   - fetchLastLevelOnly: relationQuery.parameters.fetchLastLevelOnly,
547   - type: AliasFilterType.relationsQuery
548   - } as RelationsQueryFilter;
  450 + this.cd.detectChanges();
  451 + });
549 452 }
550 453 }
... ...