Commit 92c4d00fbbefd03482a2b684401efa9e514352d3
1 parent
2eb7949d
UI: Refactor entities hierarchy widget to use new WS data query API.
Showing
5 changed files
with
138 additions
and
142 deletions
@@ -54,22 +54,6 @@ | @@ -54,22 +54,6 @@ | ||
54 | } | 54 | } |
55 | }, | 55 | }, |
56 | { | 56 | { |
57 | - "alias": "entities_hierarchy", | ||
58 | - "name": "Entities hierarchy", | ||
59 | - "descriptor": { | ||
60 | - "type": "latest", | ||
61 | - "sizeX": 7.5, | ||
62 | - "sizeY": 3.5, | ||
63 | - "resources": [], | ||
64 | - "templateHtml": "<tb-entities-hierarchy-widget \n [ctx]=\"ctx\">\n</tb-entities-hierarchy-widget>", | ||
65 | - "templateCss": "", | ||
66 | - "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesHierarchyWidget.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", | ||
67 | - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesHierarchySettings\",\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}", | ||
68 | - "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {},\n \"required\": []\n },\n \"form\": []\n}", | ||
69 | - "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: types.entitySearchDirection.from,\\n relationTypeGroup: \\\"COMMON\\\",\\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\":\"Entities hierarchy\",\"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\":{}}" | ||
70 | - } | ||
71 | - }, | ||
72 | - { | ||
73 | "alias": "html_value_card", | 57 | "alias": "html_value_card", |
74 | "name": "HTML Value Card", | 58 | "name": "HTML Value Card", |
75 | "descriptor": { | 59 | "descriptor": { |
@@ -132,6 +116,22 @@ | @@ -132,6 +116,22 @@ | ||
132 | "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", | 116 | "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"columnWidth\": {\n \"title\": \"Column width (px or %)\",\n \"type\": \"string\",\n \"default\": \"0px\"\n },\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, entity, ctx)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"columnWidth\",\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", |
133 | "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\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true},\"title\":\"Entities table\",\"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;\"}]}]}" | 117 | "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\":{\"enableSelection\":true,\"enableSearch\":true,\"displayDetails\":true,\"displayPagination\":true,\"defaultPageSize\":10,\"defaultSortOrder\":\"entityName\",\"displayEntityName\":true,\"displayEntityType\":true},\"title\":\"Entities table\",\"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;\"}]}]}" |
134 | } | 118 | } |
119 | + }, | ||
120 | + { | ||
121 | + "alias": "entities_hierarchy", | ||
122 | + "name": "Entities hierarchy", | ||
123 | + "descriptor": { | ||
124 | + "type": "latest", | ||
125 | + "sizeX": 7.5, | ||
126 | + "sizeY": 3.5, | ||
127 | + "resources": [], | ||
128 | + "templateHtml": "<tb-entities-hierarchy-widget \n [ctx]=\"ctx\">\n</tb-entities-hierarchy-widget>", | ||
129 | + "templateCss": "", | ||
130 | + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.entitiesHierarchyWidget.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", | ||
131 | + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"EntitiesHierarchySettings\",\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}", | ||
132 | + "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {},\n \"required\": []\n },\n \"form\": []\n}", | ||
133 | + "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\":\"Entities hierarchy\",\"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\":{}}" | ||
134 | + } | ||
135 | } | 135 | } |
136 | ] | 136 | ] |
137 | } | 137 | } |
@@ -8997,10 +8997,10 @@ | @@ -8997,10 +8997,10 @@ | ||
8997 | "integrity": "sha512-4O3GWAYJaauMCILm07weko2rHA8a4kjn7+8Lg4s1d7SxwS/3IpkVD/GljbRrIJ1c1W/XGJ3GbuK7RyYZEJChhw==" | 8997 | "integrity": "sha512-4O3GWAYJaauMCILm07weko2rHA8a4kjn7+8Lg4s1d7SxwS/3IpkVD/GljbRrIJ1c1W/XGJ3GbuK7RyYZEJChhw==" |
8998 | }, | 8998 | }, |
8999 | "ngx-flowchart": { | 8999 | "ngx-flowchart": { |
9000 | - "version": "git://github.com/thingsboard/ngx-flowchart.git#a4157b0eef2eb3646ef920447c7b06b39d54f87f", | 9000 | + "version": "git://github.com/thingsboard/ngx-flowchart.git#7a02f4748b5e7821a883c903107af5f20415d026", |
9001 | "from": "git://github.com/thingsboard/ngx-flowchart.git#master", | 9001 | "from": "git://github.com/thingsboard/ngx-flowchart.git#master", |
9002 | "requires": { | 9002 | "requires": { |
9003 | - "tslib": "^1.10.0" | 9003 | + "tslib": "^1.13.0" |
9004 | }, | 9004 | }, |
9005 | "dependencies": { | 9005 | "dependencies": { |
9006 | "tslib": { | 9006 | "tslib": { |
@@ -20,7 +20,7 @@ import { Datasource, DatasourceType } from '@app/shared/models/widget.models'; | @@ -20,7 +20,7 @@ import { Datasource, DatasourceType } from '@app/shared/models/widget.models'; | ||
20 | import { deepClone, isEqual } from '@core/utils'; | 20 | import { deepClone, isEqual } from '@core/utils'; |
21 | import { EntityService } from '@core/http/entity.service'; | 21 | import { EntityService } from '@core/http/entity.service'; |
22 | import { UtilsService } from '@core/services/utils.service'; | 22 | import { UtilsService } from '@core/services/utils.service'; |
23 | -import { EntityAliases } from '@shared/models/alias.models'; | 23 | +import { AliasFilterType, EntityAliases, SingleEntityFilter } from '@shared/models/alias.models'; |
24 | import { EntityInfo } from '@shared/models/entity.models'; | 24 | import { EntityInfo } from '@shared/models/entity.models'; |
25 | import { map, mergeMap } from 'rxjs/operators'; | 25 | import { map, mergeMap } from 'rxjs/operators'; |
26 | import { | 26 | import { |
@@ -282,6 +282,15 @@ export class AliasController implements IAliasController { | @@ -282,6 +282,15 @@ export class AliasController implements IAliasController { | ||
282 | } | 282 | } |
283 | }) | 283 | }) |
284 | ); | 284 | ); |
285 | + } else if (newDatasource.entityId && !newDatasource.entityFilter) { | ||
286 | + newDatasource.entityFilter = { | ||
287 | + singleEntity: { | ||
288 | + id: newDatasource.entityId, | ||
289 | + entityType: newDatasource.entityType, | ||
290 | + }, | ||
291 | + type: AliasFilterType.singleEntity | ||
292 | + } as SingleEntityFilter; | ||
293 | + return of(newDatasource); | ||
285 | } else { | 294 | } else { |
286 | newDatasource.aliasName = newDatasource.entityName; | 295 | newDatasource.aliasName = newDatasource.entityName; |
287 | newDatasource.name = newDatasource.entityName; | 296 | newDatasource.name = newDatasource.entityName; |
@@ -23,19 +23,18 @@ import { DatasourceData, DatasourceType, WidgetConfig, widgetType } from '@share | @@ -23,19 +23,18 @@ import { DatasourceData, DatasourceType, WidgetConfig, widgetType } from '@share | ||
23 | import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; | 23 | import { IWidgetSubscription, WidgetSubscriptionOptions } from '@core/api/widget-api.models'; |
24 | import { UtilsService } from '@core/services/utils.service'; | 24 | import { UtilsService } from '@core/services/utils.service'; |
25 | import cssjs from '@core/css/css'; | 25 | import cssjs from '@core/css/css'; |
26 | -import { forkJoin, fromEvent, Observable, of } from 'rxjs'; | ||
27 | -import { catchError, debounceTime, distinctUntilChanged, map, mergeMap, tap } from 'rxjs/operators'; | 26 | +import { fromEvent } from 'rxjs'; |
27 | +import { debounceTime, distinctUntilChanged, tap } from 'rxjs/operators'; | ||
28 | import { constructTableCssString } from '@home/components/widget/lib/table-widget.models'; | 28 | import { constructTableCssString } from '@home/components/widget/lib/table-widget.models'; |
29 | import { Overlay } from '@angular/cdk/overlay'; | 29 | import { Overlay } from '@angular/cdk/overlay'; |
30 | import { | 30 | import { |
31 | LoadNodesCallback, | 31 | LoadNodesCallback, |
32 | NavTreeEditCallbacks, | 32 | NavTreeEditCallbacks, |
33 | + NodesCallback, | ||
33 | NodeSearchCallback, | 34 | NodeSearchCallback, |
34 | NodeSelectedCallback, | 35 | NodeSelectedCallback, |
35 | NodesInsertedCallback | 36 | NodesInsertedCallback |
36 | } from '@shared/components/nav-tree.component'; | 37 | } from '@shared/components/nav-tree.component'; |
37 | -import { BaseData } from '@shared/models/base-data'; | ||
38 | -import { EntityId } from '@shared/models/id/entity-id'; | ||
39 | import { EntityType } from '@shared/models/entity-type.models'; | 38 | import { EntityType } from '@shared/models/entity-type.models'; |
40 | import { deepClone, hashCode } from '@core/utils'; | 39 | import { deepClone, hashCode } from '@core/utils'; |
41 | import { | 40 | import { |
@@ -58,10 +57,9 @@ import { | @@ -58,10 +57,9 @@ import { | ||
58 | NodesSortFunction, | 57 | NodesSortFunction, |
59 | NodeTextFunction | 58 | NodeTextFunction |
60 | } from '@home/components/widget/lib/entities-hierarchy-widget.models'; | 59 | } from '@home/components/widget/lib/entities-hierarchy-widget.models'; |
61 | -import { EntityService } from '@core/http/entity.service'; | ||
62 | -import { EntityRelationsQuery, EntitySearchDirection } from '@shared/models/relation.models'; | ||
63 | -import { EntityRelationService } from '@core/http/entity-relation.service'; | ||
64 | -import { ActionNotificationShow } from '@core/notification/notification.actions'; | 60 | +import { EntityRelationsQuery } from '@shared/models/relation.models'; |
61 | +import { AliasFilterType, RelationsQueryFilter } from '@shared/models/alias.models'; | ||
62 | +import { EntityFilter } from '@shared/models/query/query.models'; | ||
65 | 63 | ||
66 | @Component({ | 64 | @Component({ |
67 | selector: 'tb-entities-hierarchy-widget', | 65 | selector: 'tb-entities-hierarchy-widget', |
@@ -86,6 +84,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | @@ -86,6 +84,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | ||
86 | private widgetConfig: WidgetConfig; | 84 | private widgetConfig: WidgetConfig; |
87 | private subscription: IWidgetSubscription; | 85 | private subscription: IWidgetSubscription; |
88 | private datasources: Array<HierarchyNodeDatasource>; | 86 | private datasources: Array<HierarchyNodeDatasource>; |
87 | + private data: Array<Array<DatasourceData>>; | ||
89 | 88 | ||
90 | private nodesMap: {[nodeId: string]: HierarchyNavTreeNode} = {}; | 89 | private nodesMap: {[nodeId: string]: HierarchyNavTreeNode} = {}; |
91 | private pendingUpdateNodeTasks: {[nodeId: string]: () => void} = {}; | 90 | private pendingUpdateNodeTasks: {[nodeId: string]: () => void} = {}; |
@@ -108,14 +107,11 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | @@ -108,14 +107,11 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | ||
108 | } | 107 | } |
109 | }; | 108 | }; |
110 | 109 | ||
111 | - | ||
112 | constructor(protected store: Store<AppState>, | 110 | constructor(protected store: Store<AppState>, |
113 | private elementRef: ElementRef, | 111 | private elementRef: ElementRef, |
114 | private overlay: Overlay, | 112 | private overlay: Overlay, |
115 | private viewContainerRef: ViewContainerRef, | 113 | private viewContainerRef: ViewContainerRef, |
116 | - private utils: UtilsService, | ||
117 | - private entityService: EntityService, | ||
118 | - private entityRelationService: EntityRelationService) { | 114 | + private utils: UtilsService) { |
119 | super(store); | 115 | super(store); |
120 | } | 116 | } |
121 | 117 | ||
@@ -125,6 +121,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | @@ -125,6 +121,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | ||
125 | this.widgetConfig = this.ctx.widgetConfig; | 121 | this.widgetConfig = this.ctx.widgetConfig; |
126 | this.subscription = this.ctx.defaultSubscription; | 122 | this.subscription = this.ctx.defaultSubscription; |
127 | this.datasources = this.subscription.datasources as Array<HierarchyNodeDatasource>; | 123 | this.datasources = this.subscription.datasources as Array<HierarchyNodeDatasource>; |
124 | + this.data = this.subscription.dataPages[0].data; | ||
128 | this.initializeConfig(); | 125 | this.initializeConfig(); |
129 | this.ctx.updateWidgetParams(); | 126 | this.ctx.updateWidgetParams(); |
130 | } | 127 | } |
@@ -254,33 +251,15 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | @@ -254,33 +251,15 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | ||
254 | 251 | ||
255 | public loadNodes: LoadNodesCallback = (node, cb) => { | 252 | public loadNodes: LoadNodesCallback = (node, cb) => { |
256 | if (node.id === '#') { | 253 | if (node.id === '#') { |
257 | - const tasks: Observable<HierarchyNavTreeNode>[] = []; | ||
258 | - this.datasources.forEach((datasource) => { | ||
259 | - tasks.push(this.datasourceToNode(datasource)); | ||
260 | - }); | ||
261 | - forkJoin(tasks).subscribe((nodes) => { | ||
262 | - cb(this.prepareNodes(nodes)); | ||
263 | - this.updateNodeData(this.subscription.data); | 254 | + const childNodes: HierarchyNavTreeNode[] = []; |
255 | + this.datasources.forEach((childDatasource, index) => { | ||
256 | + childNodes.push(this.datasourceToNode(childDatasource as HierarchyNodeDatasource, this.data[index])); | ||
264 | }); | 257 | }); |
258 | + cb(this.prepareNodes(childNodes)); | ||
265 | } else { | 259 | } else { |
266 | if (node.data && node.data.nodeCtx.entity && node.data.nodeCtx.entity.id && node.data.nodeCtx.entity.id.entityType !== 'function') { | 260 | if (node.data && node.data.nodeCtx.entity && node.data.nodeCtx.entity.id && node.data.nodeCtx.entity.id.entityType !== 'function') { |
267 | - const relationQuery = this.prepareNodeRelationQuery(node.data.nodeCtx); | ||
268 | - this.entityRelationService.findByQuery(relationQuery, {ignoreErrors: true, ignoreLoading: true}).subscribe( | ||
269 | - (entityRelations) => { | ||
270 | - if (entityRelations.length) { | ||
271 | - const tasks: Observable<HierarchyNavTreeNode>[] = []; | ||
272 | - entityRelations.forEach((relation) => { | ||
273 | - const targetId = relationQuery.parameters.direction === EntitySearchDirection.FROM ? relation.to : relation.from; | ||
274 | - tasks.push(this.entityIdToNode(targetId.entityType as EntityType, targetId.id, node.data.datasource, node.data.nodeCtx)); | ||
275 | - }); | ||
276 | - forkJoin(tasks).subscribe((nodes) => { | ||
277 | - cb(this.prepareNodes(nodes)); | ||
278 | - }); | ||
279 | - } else { | ||
280 | - cb([]); | ||
281 | - } | ||
282 | - }, | ||
283 | - (error) => { | 261 | + this.loadChildren(node, node.data.datasource, cb); |
262 | + /* (error) => { // TODO: | ||
284 | let errorText = 'Failed to get relations!'; | 263 | let errorText = 'Failed to get relations!'; |
285 | if (error && error.status === 400) { | 264 | if (error && error.status === 400) { |
286 | errorText = 'Invalid relations query returned by \'Node relations query function\'! Please check widget configuration!'; | 265 | 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 | @@ -288,6 +267,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | ||
288 | this.showError(errorText); | 267 | this.showError(errorText); |
289 | } | 268 | } |
290 | ); | 269 | ); |
270 | + */ | ||
291 | } else { | 271 | } else { |
292 | cb([]); | 272 | cb([]); |
293 | } | 273 | } |
@@ -313,7 +293,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | @@ -313,7 +293,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | ||
313 | } | 293 | } |
314 | } | 294 | } |
315 | 295 | ||
316 | - public onNodesInserted: NodesInsertedCallback = (nodes, parent) => { | 296 | + public onNodesInserted: NodesInsertedCallback = (nodes) => { |
317 | if (nodes) { | 297 | if (nodes) { |
318 | nodes.forEach((nodeId) => { | 298 | nodes.forEach((nodeId) => { |
319 | const task = this.pendingUpdateNodeTasks[nodeId]; | 299 | const task = this.pendingUpdateNodeTasks[nodeId]; |
@@ -355,17 +335,6 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | @@ -355,17 +335,6 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | ||
355 | } | 335 | } |
356 | } | 336 | } |
357 | 337 | ||
358 | - private showError(errorText: string) { | ||
359 | - this.store.dispatch(new ActionNotificationShow( | ||
360 | - { | ||
361 | - message: errorText, | ||
362 | - type: 'error', | ||
363 | - target: this.toastTargetId, | ||
364 | - verticalPosition: 'bottom', | ||
365 | - horizontalPosition: 'left' | ||
366 | - })); | ||
367 | - } | ||
368 | - | ||
369 | private prepareNodes(nodes: HierarchyNavTreeNode[]): HierarchyNavTreeNode[] { | 338 | private prepareNodes(nodes: HierarchyNavTreeNode[]): HierarchyNavTreeNode[] { |
370 | nodes = nodes.filter((node) => node !== null); | 339 | nodes = nodes.filter((node) => node !== null); |
371 | nodes.sort((node1, node2) => this.nodesSortFunction(node1.data.nodeCtx, node2.data.nodeCtx)); | 340 | nodes.sort((node1, node2) => this.nodesSortFunction(node1.data.nodeCtx, node2.data.nodeCtx)); |
@@ -399,85 +368,87 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | @@ -399,85 +368,87 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | ||
399 | } | 368 | } |
400 | } | 369 | } |
401 | 370 | ||
402 | - private datasourceToNode(datasource: HierarchyNodeDatasource, parentNodeCtx?: HierarchyNodeContext): Observable<HierarchyNavTreeNode> { | ||
403 | - return this.resolveEntity(datasource).pipe( | ||
404 | - map(entity => { | ||
405 | - if (entity !== null) { | ||
406 | - const node: HierarchyNavTreeNode = { | ||
407 | - id: (++this.nodeIdCounter) + '' | ||
408 | - }; | ||
409 | - this.nodesMap[node.id] = node; | ||
410 | - datasource.nodeId = node.id; | ||
411 | - node.icon = false; | ||
412 | - const nodeCtx: HierarchyNodeContext = { | ||
413 | - parentNodeCtx, | ||
414 | - entity, | ||
415 | - data: {} | ||
416 | - }; | ||
417 | - nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1; | ||
418 | - node.data = { | ||
419 | - datasource, | ||
420 | - nodeCtx | ||
421 | - }; | ||
422 | - node.state = { | ||
423 | - disabled: this.nodeDisabledFunction(node.data.nodeCtx), | ||
424 | - opened: this.nodeOpenedFunction(node.data.nodeCtx) | ||
425 | - }; | ||
426 | - node.text = this.prepareNodeText(node); | ||
427 | - node.children = this.nodeHasChildrenFunction(node.data.nodeCtx); | ||
428 | - return node; | ||
429 | - } else { | ||
430 | - return null; | ||
431 | - } | ||
432 | - }) | ||
433 | - ); | 371 | + private datasourceToNode(datasource: HierarchyNodeDatasource, |
372 | + data: DatasourceData[], | ||
373 | + parentNodeCtx?: HierarchyNodeContext): HierarchyNavTreeNode { | ||
374 | + const node: HierarchyNavTreeNode = { | ||
375 | + id: (++this.nodeIdCounter) + '' | ||
376 | + }; | ||
377 | + this.nodesMap[node.id] = node; | ||
378 | + datasource.nodeId = node.id; | ||
379 | + node.icon = false; | ||
380 | + const nodeCtx: HierarchyNodeContext = { | ||
381 | + parentNodeCtx, | ||
382 | + entity: { | ||
383 | + id: { | ||
384 | + id: datasource.entityId, | ||
385 | + entityType: datasource.entityType | ||
386 | + }, | ||
387 | + name: datasource.entityName, | ||
388 | + label: datasource.entityLabel ? datasource.entityLabel : datasource.entityName | ||
389 | + }, | ||
390 | + data: {} | ||
391 | + }; | ||
392 | + datasource.dataKeys.forEach((dataKey, index) => { | ||
393 | + const keyData = data[index].data; | ||
394 | + if (keyData && keyData.length && keyData[0].length > 1) { | ||
395 | + nodeCtx.data[dataKey.label] = keyData[0][1]; | ||
396 | + } else { | ||
397 | + nodeCtx.data[dataKey.label] = ''; | ||
398 | + } | ||
399 | + }); | ||
400 | + nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1; | ||
401 | + node.data = { | ||
402 | + datasource, | ||
403 | + nodeCtx | ||
404 | + }; | ||
405 | + node.state = { | ||
406 | + disabled: this.nodeDisabledFunction(node.data.nodeCtx), | ||
407 | + opened: this.nodeOpenedFunction(node.data.nodeCtx) | ||
408 | + }; | ||
409 | + node.text = this.prepareNodeText(node); | ||
410 | + node.children = this.nodeHasChildrenFunction(node.data.nodeCtx); | ||
411 | + return node; | ||
434 | } | 412 | } |
435 | 413 | ||
436 | - private entityIdToNode(entityType: EntityType, entityId: string, | ||
437 | - parentDatasource: HierarchyNodeDatasource, | ||
438 | - parentNodeCtx: HierarchyNodeContext): Observable<HierarchyNavTreeNode> { | ||
439 | - const datasource = { | ||
440 | - dataKeys: parentDatasource.dataKeys, | 414 | + private loadChildren(parentNode: HierarchyNavTreeNode, datasource: HierarchyNodeDatasource, childrenNodesLoadCb: NodesCallback) { |
415 | + const nodeCtx = parentNode.data.nodeCtx; | ||
416 | + nodeCtx.childrenNodesLoaded = false; | ||
417 | + const entityFilter = this.prepareNodeRelationsQueryFilter(nodeCtx); | ||
418 | + const childrenDatasource = { | ||
419 | + dataKeys: datasource.dataKeys, | ||
441 | type: DatasourceType.entity, | 420 | type: DatasourceType.entity, |
442 | - entityType, | ||
443 | - entityId | 421 | + entityFilter |
444 | } as HierarchyNodeDatasource; | 422 | } as HierarchyNodeDatasource; |
445 | - return this.datasourceToNode(datasource, parentNodeCtx).pipe( | ||
446 | - mergeMap((node) => { | ||
447 | - if (node != null) { | ||
448 | - const subscriptionOptions: WidgetSubscriptionOptions = { | ||
449 | - type: widgetType.latest, | ||
450 | - datasources: [datasource], | ||
451 | - callbacks: { | ||
452 | - onDataUpdated: subscription => { | ||
453 | - this.updateNodeData(subscription.data); | ||
454 | - } | ||
455 | - } | ||
456 | - }; | ||
457 | - return this.ctx.subscriptionApi. | ||
458 | - createSubscription(subscriptionOptions, true).pipe( | ||
459 | - map(() => node)); | ||
460 | - } else { | ||
461 | - return of(node); | ||
462 | - } | ||
463 | - }) | ||
464 | - ); | ||
465 | - } | ||
466 | - | ||
467 | - private resolveEntity(datasource: HierarchyNodeDatasource): Observable<BaseData<EntityId>> { | ||
468 | - if (datasource.type === DatasourceType.function) { | ||
469 | - const entity = { | ||
470 | - id: { | ||
471 | - entityType: 'function' | 423 | + const subscriptionOptions: WidgetSubscriptionOptions = { |
424 | + type: widgetType.latest, | ||
425 | + datasources: [childrenDatasource], | ||
426 | + callbacks: { | ||
427 | + onSubscriptionMessage: (subscription, message) => { | ||
428 | + this.ctx.showToast(message.severity, message.message, undefined, | ||
429 | + 'bottom', 'left', this.toastTargetId); | ||
472 | }, | 430 | }, |
473 | - name: datasource.name | ||
474 | - }; | ||
475 | - return of(entity as BaseData<EntityId>); | ||
476 | - } else { | ||
477 | - return this.entityService.getEntity(datasource.entityType, datasource.entityId, {ignoreLoading: true}).pipe( | ||
478 | - catchError(err => of(null)) | ||
479 | - ); | ||
480 | - } | 431 | + onInitialPageDataChanged: (subscription) => { |
432 | + this.ctx.subscriptionApi.removeSubscription(subscription.id); | ||
433 | + this.nodeEditCallbacks.refreshNode(parentNode.id); | ||
434 | + }, | ||
435 | + onDataUpdated: subscription => { | ||
436 | + if (nodeCtx.childrenNodesLoaded) { | ||
437 | + this.updateNodeData(subscription.data); | ||
438 | + } else { | ||
439 | + const datasourcesPageData = subscription.datasourcePages[0]; | ||
440 | + const dataPageData = subscription.dataPages[0]; | ||
441 | + const childNodes: HierarchyNavTreeNode[] = []; | ||
442 | + datasourcesPageData.data.forEach((childDatasource, index) => { | ||
443 | + childNodes.push(this.datasourceToNode(childDatasource as HierarchyNodeDatasource, dataPageData.data[index])); | ||
444 | + }); | ||
445 | + nodeCtx.childrenNodesLoaded = true; | ||
446 | + childrenNodesLoadCb(this.prepareNodes(childNodes)); | ||
447 | + } | ||
448 | + } | ||
449 | + } | ||
450 | + }; | ||
451 | + this.ctx.subscriptionApi.createSubscription(subscriptionOptions, true); | ||
481 | } | 452 | } |
482 | 453 | ||
483 | private prepareNodeRelationQuery(nodeCtx: HierarchyNodeContext): EntityRelationsQuery { | 454 | private prepareNodeRelationQuery(nodeCtx: HierarchyNodeContext): EntityRelationsQuery { |
@@ -487,4 +458,19 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | @@ -487,4 +458,19 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O | ||
487 | } | 458 | } |
488 | return relationQuery as EntityRelationsQuery; | 459 | return relationQuery as EntityRelationsQuery; |
489 | } | 460 | } |
461 | + | ||
462 | + private prepareNodeRelationsQueryFilter(nodeCtx: HierarchyNodeContext): EntityFilter { | ||
463 | + const relationQuery = this.prepareNodeRelationQuery(nodeCtx); | ||
464 | + return { | ||
465 | + rootEntity: { | ||
466 | + id: relationQuery.parameters.rootId, | ||
467 | + entityType: relationQuery.parameters.rootType | ||
468 | + }, | ||
469 | + direction: relationQuery.parameters.direction, | ||
470 | + filters: relationQuery.filters, | ||
471 | + maxLevel: relationQuery.parameters.maxLevel, | ||
472 | + fetchLastLevelOnly: relationQuery.parameters.fetchLastLevelOnly, | ||
473 | + type: AliasFilterType.relationsQuery | ||
474 | + } as RelationsQueryFilter; | ||
475 | + } | ||
490 | } | 476 | } |
@@ -16,7 +16,7 @@ | @@ -16,7 +16,7 @@ | ||
16 | 16 | ||
17 | import { BaseData } from '@shared/models/base-data'; | 17 | import { BaseData } from '@shared/models/base-data'; |
18 | import { EntityId } from '@shared/models/id/entity-id'; | 18 | import { EntityId } from '@shared/models/id/entity-id'; |
19 | -import { NavTreeNode } from '@shared/components/nav-tree.component'; | 19 | +import { NavTreeNode, NodesCallback } from '@shared/components/nav-tree.component'; |
20 | import { Datasource } from '@shared/models/widget.models'; | 20 | import { Datasource } from '@shared/models/widget.models'; |
21 | import { isDefined, isUndefined } from '@core/utils'; | 21 | import { isDefined, isUndefined } from '@core/utils'; |
22 | import { EntityRelationsQuery, EntitySearchDirection, RelationTypeGroup } from '@shared/models/relation.models'; | 22 | import { EntityRelationsQuery, EntitySearchDirection, RelationTypeGroup } from '@shared/models/relation.models'; |
@@ -35,6 +35,7 @@ export interface EntitiesHierarchyWidgetSettings { | @@ -35,6 +35,7 @@ export interface EntitiesHierarchyWidgetSettings { | ||
35 | export interface HierarchyNodeContext { | 35 | export interface HierarchyNodeContext { |
36 | parentNodeCtx?: HierarchyNodeContext; | 36 | parentNodeCtx?: HierarchyNodeContext; |
37 | entity: BaseData<EntityId>; | 37 | entity: BaseData<EntityId>; |
38 | + childrenNodesLoaded?: boolean; | ||
38 | level?: number; | 39 | level?: number; |
39 | data: {[key: string]: any}; | 40 | data: {[key: string]: any}; |
40 | } | 41 | } |