From 0bf75304ce2ff641c75c257e5b6557d7ac12d7a2 Mon Sep 17 00:00:00 2001 From: Alex Bourret Date: Thu, 18 Dec 2025 14:54:27 +0100 Subject: [PATCH 01/17] Adding search option --- js/pi-system_treecontroller.js | 54 ++++----------- python-lib/osisoft_client.py | 55 +++++++++++++++ python-lib/osisoft_plugin_common.py | 102 ++++++++++++++++++++++++++++ resource/browse_af_tree.py | 65 ++++++++++++++---- resource/pi-system_af-explorer.html | 10 +-- 5 files changed, 230 insertions(+), 56 deletions(-) diff --git a/js/pi-system_treecontroller.js b/js/pi-system_treecontroller.js index 9d73b1b..ab70d03 100644 --- a/js/pi-system_treecontroller.js +++ b/js/pi-system_treecontroller.js @@ -8,6 +8,10 @@ app.controller('TreeCtrl', ['$scope', '$http','CreateModalFromTemplate', functio console.log($scope.treeData); }) + $scope.refreshTree = function(newTree) { + $scope.treeData = newTree.choices + } + // Toggle récursif des checkboxes $scope.toggleChildren = function(node) { console.log("ALX:tc:" + JSON.stringify(node)); @@ -30,7 +34,16 @@ app.controller('TreeCtrl', ['$scope', '$http','CreateModalFromTemplate', functio child.expanded = false; }); }); - } + }; + + $scope.doSearch = function(element_name, attribute_name){ + console.log("ALX:search for ", element_name, attribute_name); + $scope.callPythonDo({method: "do_search", element_name: element_name, attribute_name: attribute_name}).then( + function(data){ + $scope.treeData = data.choices; + } + ); + }; }]); app.controller('AfExplorerFormController', function($scope, $stateParams, CodeMirrorSettingService) { @@ -62,59 +75,22 @@ app.controller('AfExplorerFormController', function($scope, $stateParams, CodeMi }).error(setErrorInScope.bind($scope.errorScope)); }; - $scope.getServers = function(noken){ - console.log("ALX:get servers"); - console.log("ALX:" + JSON.stringify(noken)); + $scope.getServers = function(){ $scope.callPythonDo({parameterName: "server_name"}).then(function(data){ - console.log("ALX:getServers return:"+JSON.stringify(data)) // $scope.config["server_name"] = data.choices; $scope.server_name = data.choices; }); }; $scope.getDatabases = function() { $scope.callPythonDo({parameterName: "database_name"}).then(function(data){ - console.log("ALX:getDatabases return:"+JSON.stringify(data)) $scope.database_name = data.choices; }); }; $scope.initializeTree = function(){ - console.log("ALX:initializeTree:scope=" + JSON.stringify($scope.config.database_name)); $scope.callPythonDo({method: "get_children_from_db", parent: $scope.config.database_name}).then(function(data){ console.log("ALX:data2=" + JSON.stringify(data)); }); }; - $scope.doSearch = function(element_name, attribute_name){ - console.log("ALX:search for ", element_name, attribute_name); - $scope.callPythonDo({method: "do_search", element_name: element_name, attribute_name: attribute_name}).then( - function(data){ - console.log("ALX:search result:", JSON.stringify(data)); - // $scope.treeData = data.choices; - $scope.$parent.treeData = [ - { - "id": 1, - "title": "Elements", - "expanded": false, - "checked": false, - "children": [ - { - "id": "blabla", - "url": "xcvb", - "title": "Well", - "expanded": false, - "checked": false, - "children": [ - ] - } - ] - } - ]; - // item.children.forEach(child => { - // child.checked = item.checked; - // child.expanded = false; - // }); - } - ); - }; }); diff --git a/python-lib/osisoft_client.py b/python-lib/osisoft_client.py index 955e411..493b8bc 100644 --- a/python-lib/osisoft_client.py +++ b/python-lib/osisoft_client.py @@ -13,6 +13,7 @@ iso_to_epoch, RecordsLimit, is_iso8601, get_next_page_url, change_key_in_dict, BatchTimeCounter ) +from osisoft_plugin_common import get_item_details from osisoft_pagination import OffsetPagination from safe_logger import SafeLogger @@ -810,6 +811,60 @@ def traverse(self, path_elements): json_response = self.get(url=next_url, headers=headers, params={}, error_source="traverse") if attribute: item = self.extract_item_with_name(json_response, attribute) + return item + + def traverse_and_cache(self, path_elements, path_attributes, tree): + print("ALX:traverse_and_cache:path_elements={}, path_attributes={}".format(path_elements, path_attributes)) + full_path_elements = path_elements.copy() + path_attributes.copy() + # Loading piwebapi initial page + next_url = self.endpoint.get_base_url() + headers = self.get_requests_headers() + json_response = self.get(url=next_url, headers=headers, params={}, error_source="traverse") + + # Asset server page + next_url = self.extract_link_with_key(json_response, "AssetServers") + json_response = self.get(url=next_url, headers=headers, params={}, error_source="traverse") + + item = self.extract_item_with_name(json_response, path_elements.pop(0)) + tree.put(full_path_elements[0:1], get_item_details(item)) + next_url = self.extract_link_with_key(item, "Databases") + json_response = self.get(url=next_url, headers=headers, params={}, error_source="traverse") + + # retrieved_from_cache = tree.get(full_path_elements[0:2], {}).get("url")+"/elements" + # get the database + item = self.extract_item_with_name(json_response, path_elements.pop(0)) + tree.put(full_path_elements[0:2], get_item_details(item)) + next_url = self.extract_link_with_key(item, "Elements") + # print("ALX:database:next_url={}, retrieved_from_cache={}".format(next_url, retrieved_from_cache)) + json_response = self.get(url=next_url, headers=headers, params={}, error_source="traverse") + + # Looping through elements + counter = 3 + before_last_url = None + for path_element in path_elements: + element, attribute = self.split_element_attribute(path_element) + item = self.extract_item_with_name(json_response, element) + tree.put(full_path_elements[0:counter], get_item_details(item)) + counter += 1 + before_last_url = self.extract_link_with_key(item, "Attributes") + if attribute: + next_url = self.extract_link_with_key(item, "Attributes") + else: + next_url = self.extract_link_with_key(item, "Elements") + json_response = self.get(url=next_url, headers=headers, params={}, error_source="traverse") + if attribute: + item = self.extract_item_with_name(json_response, attribute) + json_response = self.get(url=before_last_url, headers=headers, params={}, error_source="traverse") + for path_attribute in path_attributes: + # print("ALX:extract '{}' from {}".format(path_attribute, json_response)) + item = self.extract_item_with_name(json_response, path_attribute) + tree.put(full_path_elements[0:counter], get_item_details(item)) + counter += 1 + next_url = self.extract_link_with_key(item, "Attributes") + if next_url: + json_response = self.get(url=next_url, headers=headers, params={}, error_source="traverse") + else: + break return item diff --git a/python-lib/osisoft_plugin_common.py b/python-lib/osisoft_plugin_common.py index 6f8b8ed..4e2bd51 100644 --- a/python-lib/osisoft_plugin_common.py +++ b/python-lib/osisoft_plugin_common.py @@ -634,3 +634,105 @@ def is_batch_full(self): def add(self, start_time, end_time, interval): self.total_batched_time += compute_time_spent(start_time, end_time, interval) + + +def get_item_details(item): + KEYS_TO_CHECK = { + "Name": "title", "TemplateName": "template_name", "CategoryNames": "category_names", + "HasChildren": "has_children", "Path": "path", "WebId": "id" + } + details = {} + for key_to_check in KEYS_TO_CHECK: + value = item.get(key_to_check) + if value: + details[KEYS_TO_CHECK.get(key_to_check)] = value + details["url"] = item.get("Links", {}).get("Self") + return details + + +class Tree(): + # Each put + # - stores the data in the index + # - builds a tree based on the data's path, pointing at the right index + def __init__(self): + self.tree = {} + self.index = [] + + def put(self, path, data): + if isinstance(path, list): + current_level = self.tree + for token in path: + if token not in current_level: + current_level[token] = {} + current_level = current_level.get(token) + index_to_update = current_level.get("_v", None) + if index_to_update is not None: + self.index[index_to_update] = data + else: + last_index = len(self.index) + self.index.append(data) + current_level.update({"_v": last_index}) + + def get(self, path, default=None): + if isinstance(path, list): + current_level = self.tree + for token in path: + if token not in current_level: + return default + else: + current_level = current_level.get(token) + index = current_level.get("_v") + return self.get_record(index) + + def get_tree(self): + return self.tree + + def get_record(self, index): + if index < len(self.index): + return self.index[index] + return None + + def get_records(self): + return self.index + + def exists(self, path): + current = self.tree + if isinstance(path, list): + for token in path: + current = current.get(token, {}) + if not current: + return False + return True + return False + + def print(self): + print("Tree {}".format(self.tree)) + print("Tree content {}".format(self.index)) + + +def recursive_tree_rebuild(dictionary, records, counter=None): + counter = counter or -1 + output = [] + + for key in dictionary: + if key == "_v": + continue + sub_dictionary = dictionary.get(key) + context = {} + if "_v" in sub_dictionary: + index_id = sub_dictionary.get("_v") + if isinstance(index_id, int): + context = records[index_id] + counter += 1 + if sub_dictionary: + counter += 1 + children = recursive_tree_rebuild(sub_dictionary, records, counter + 1) + else: + children = [] + context["id"] = str(counter) + context["title"] = key + context["expanded"] = True + context["checked"] = False + context["children"] = children + output.append(context) + return output diff --git a/resource/browse_af_tree.py b/resource/browse_af_tree.py index 929a9b7..2fb8fef 100644 --- a/resource/browse_af_tree.py +++ b/resource/browse_af_tree.py @@ -1,5 +1,6 @@ from osisoft_client import OSIsoftClient from osisoft_plugin_common import get_credentials, build_select_choices, check_debug_mode +from osisoft_plugin_common import get_item_details, Tree, recursive_tree_rebuild import dataiku @@ -61,9 +62,10 @@ def do(payload, config, plugin_config, inputs): database_webid = database_name.split("/")[-1] for attribute in client.search_attributes( database_webid, attribute_name=attribute_name, element_name=element_name): - print("ALX:attribute={}".format(attribute)) + # print("ALX:attribute={}".format(attribute)) attributes.append(attribute) - return {"choices": attributes} + rebuilt_tree = rebuild_tree(client, attributes) + return {"choices": rebuilt_tree} parameter_name = payload.get("parameterName") @@ -118,6 +120,7 @@ def get_children_from_db(client, parent_node, database_name=None): elements = client.get_next_item_from_url(elements_url) for element in elements: child = get_item_details(element) + # child["title"] = "🧩{}".format(child.get("title")) child["type"] = "element" child["children"] = [] children.append(child) @@ -125,6 +128,7 @@ def get_children_from_db(client, parent_node, database_name=None): attributes = client.get_next_item_from_url(attributes_url) for attribute in attributes: child = get_item_details(attribute) + # child["title"] = "🏷️{}".format(child.get("title")) child["type"] = "attribute" if child.get("has_children"): child["children"] = [] @@ -132,13 +136,50 @@ def get_children_from_db(client, parent_node, database_name=None): return {"choices": children} - -def get_item_details(item): - KEYS_TO_CHECK = {"Name": "title", "TemplateName": "template_name", "CategoryNames": "category_names", "HasChildren": "has_children", "Path": "path", "WebId": "id"} - details = {} - for key_to_check in KEYS_TO_CHECK: - value = item.get(key_to_check) - if value: - details[KEYS_TO_CHECK.get(key_to_check)] = value - details["url"] = item.get("Links", {}).get("Self") - return details +# method2: +# we dig, but this time it's index[token name], and we store as we go in the child, with the real data indexed in a list and just the rank pointing to it +# to build the final tree, we browse the index, get the index data, rebuild the struct from there +# Tree class ? put(path, data), get(path, data) + + +def rebuild_tree(client, items): + # builds an active tree containing all the items and their parent up to the root + # print("ALX:items={}".format(len(items))) + tree = Tree() + final_tree = [] + while len(items) > 1: + # print("ALX:items={}".format(items)) + item = items.pop() + if item is None: + print("ALX:end") + break + # print("ALX:searching ancestors for {}".format(item)) + # tree.put(path_to_list(item.get("Path")), get_item_details(item)) + all_item_s_ancestors = find_all_ancestors(client, item, tree) + print("ALX:found {} ancestors".format(len(all_item_s_ancestors))) + final_tree = combine_trees(final_tree, all_item_s_ancestors) + # tree.print() + result = recursive_tree_rebuild(tree.get_tree(), tree.get_records()) + # print("ALX:result={}".format(result)) + return result + + +def find_all_ancestors(client, item, tree): + # Find all the ancestors of an item + elements_paths_tokens, attributes_paths_tokens = path_to_list(item.get("Path")) + # print("ALX:search {}/{}".format(elements_paths_tokens, attributes_paths_tokens)) + parent_item = client.traverse_and_cache(elements_paths_tokens, attributes_paths_tokens, tree) + # print("ALX:parent_item={}".format(parent_item)) + return [] + + +def combine_trees(final_tree, all_item_s_ancestors): + # combine two trees with partial overlap and common root ancestor + return final_tree + + +# elements, attributes +def path_to_list(path): + if not path: + return [] + return path.split('|')[0].split('\\')[2:], (path.split('|')[1:]) diff --git a/resource/pi-system_af-explorer.html b/resource/pi-system_af-explorer.html index 6d67d20..140cecf 100644 --- a/resource/pi-system_af-explorer.html +++ b/resource/pi-system_af-explorer.html @@ -37,17 +37,17 @@ - + +
+ -
- -
-
{{treeData | json}} + From a456e21c19771c2edb77ff825489d6b226403276 Mon Sep 17 00:00:00 2001 From: Alex Bourret Date: Mon, 22 Dec 2025 16:52:08 +0100 Subject: [PATCH 02/17] insert the result search into existing tree --- js/pi-system_treecontroller.js | 3 +-- python-lib/osisoft_plugin_common.py | 14 +++++++++- resource/browse_af_tree.py | 40 +++++++++++++++++------------ 3 files changed, 37 insertions(+), 20 deletions(-) diff --git a/js/pi-system_treecontroller.js b/js/pi-system_treecontroller.js index 61184d7..0156bf6 100644 --- a/js/pi-system_treecontroller.js +++ b/js/pi-system_treecontroller.js @@ -136,8 +136,7 @@ app.controller('AfExplorerFormCtrl', [ }; $scope.doSearch = function(element_name, attribute_name){ - console.log("ALX:search for ", element_name, attribute_name); - $scope.callPythonDo({method: "do_search", element_name: element_name, attribute_name: attribute_name}).then( + $scope.callPythonDo({method: "do_search", element_name: element_name, attribute_name: attribute_name, root_tree: $scope.treeData}).then( function(data){ $scope.treeData = data.choices; } diff --git a/python-lib/osisoft_plugin_common.py b/python-lib/osisoft_plugin_common.py index 4e2bd51..e7a1cb2 100644 --- a/python-lib/osisoft_plugin_common.py +++ b/python-lib/osisoft_plugin_common.py @@ -654,9 +654,21 @@ class Tree(): # Each put # - stores the data in the index # - builds a tree based on the data's path, pointing at the right index - def __init__(self): + def __init__(self, root_tree=None): self.tree = {} self.index = [] + if root_tree: + self._ingest(root_tree) + + def _ingest(self, root_tree): + if isinstance(root_tree, list): + for item in root_tree: + item_children = item.pop("children", []) + if item_children: + self._ingest(item_children) + path = item.get("path", "") + path_tokens = path.replace("|", "\\").split("\\")[2:] + self.put(path_tokens, item) def put(self, path, data): if isinstance(path, list): diff --git a/resource/browse_af_tree.py b/resource/browse_af_tree.py index 2fb8fef..fbe4b66 100644 --- a/resource/browse_af_tree.py +++ b/resource/browse_af_tree.py @@ -57,6 +57,7 @@ def do(payload, config, plugin_config, inputs): database_name = config.get("database_name") element_name = config.get("element_name") attribute_name = config.get("attribute_name") + root_tree = payload.get("root_tree") attributes = [] # https://dku-qa-osi.francecentral.cloudapp.azure.com/piwebapi/assetdatabases/F1RD3VEt1yTvt0ip6-a5yeEVsgbMcrwu_Je0qg9btcZIvPswT1NJU09GVC1QSS1TRVJWXFdFTEw database_webid = database_name.split("/")[-1] @@ -64,7 +65,7 @@ def do(payload, config, plugin_config, inputs): database_webid, attribute_name=attribute_name, element_name=element_name): # print("ALX:attribute={}".format(attribute)) attributes.append(attribute) - rebuilt_tree = rebuild_tree(client, attributes) + rebuilt_tree = rebuild_tree(client, attributes, root_tree) return {"choices": rebuilt_tree} parameter_name = payload.get("parameterName") @@ -142,35 +143,40 @@ def get_children_from_db(client, parent_node, database_name=None): # Tree class ? put(path, data), get(path, data) -def rebuild_tree(client, items): +def rebuild_tree(client, items, root_tree=None): # builds an active tree containing all the items and their parent up to the root - # print("ALX:items={}".format(len(items))) - tree = Tree() - final_tree = [] + tree = Tree(root_tree=root_tree) + tree.print() while len(items) > 1: - # print("ALX:items={}".format(items)) item = items.pop() if item is None: print("ALX:end") break - # print("ALX:searching ancestors for {}".format(item)) - # tree.put(path_to_list(item.get("Path")), get_item_details(item)) - all_item_s_ancestors = find_all_ancestors(client, item, tree) - print("ALX:found {} ancestors".format(len(all_item_s_ancestors))) - final_tree = combine_trees(final_tree, all_item_s_ancestors) - # tree.print() + find_all_ancestors(client, item, tree) result = recursive_tree_rebuild(tree.get_tree(), tree.get_records()) - # print("ALX:result={}".format(result)) + result = drop_first_levels(result) return result +def drop_first_levels(result): + # recursively removes the 2 first levels of the returned tree + # (server and DB) + output_result = [] + for item in result: + path = item.get("path", "") + path_length = len(path.split("\\")) + if path_length >= 5: + output_result.append(item) + else: + children = item.get("children", []) + output_result = drop_first_levels(children) + return output_result + + def find_all_ancestors(client, item, tree): # Find all the ancestors of an item elements_paths_tokens, attributes_paths_tokens = path_to_list(item.get("Path")) - # print("ALX:search {}/{}".format(elements_paths_tokens, attributes_paths_tokens)) - parent_item = client.traverse_and_cache(elements_paths_tokens, attributes_paths_tokens, tree) - # print("ALX:parent_item={}".format(parent_item)) - return [] + client.traverse_and_cache(elements_paths_tokens, attributes_paths_tokens, tree) def combine_trees(final_tree, all_item_s_ancestors): From 125f8c7968495905cdc1090098d4d90ae72c4b31 Mon Sep 17 00:00:00 2001 From: Alex Bourret Date: Tue, 6 Jan 2026 11:15:35 +0100 Subject: [PATCH 03/17] fix on search --- js/pi-system_treecontroller.js | 6 +++--- python-lib/osisoft_client.py | 4 +++- python-lib/osisoft_plugin_common.py | 17 +++++++++------- resource/browse_af_tree.py | 30 ++++++++++++++++++++++++++++- 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/js/pi-system_treecontroller.js b/js/pi-system_treecontroller.js index 0156bf6..f95512e 100644 --- a/js/pi-system_treecontroller.js +++ b/js/pi-system_treecontroller.js @@ -156,8 +156,8 @@ app.directive('treeNode', function() { - â–Ľ - â–¶ + â–Ľ + â–¶ @@ -170,7 +170,7 @@ app.directive('treeNode', function() { -
    +
    • diff --git a/python-lib/osisoft_client.py b/python-lib/osisoft_client.py index 493b8bc..ff75709 100644 --- a/python-lib/osisoft_client.py +++ b/python-lib/osisoft_client.py @@ -858,7 +858,9 @@ def traverse_and_cache(self, path_elements, path_attributes, tree): for path_attribute in path_attributes: # print("ALX:extract '{}' from {}".format(path_attribute, json_response)) item = self.extract_item_with_name(json_response, path_attribute) - tree.put(full_path_elements[0:counter], get_item_details(item)) + item_details = get_item_details(item) + item_details["checked"] = True + tree.put(full_path_elements[0:counter], item_details) counter += 1 next_url = self.extract_link_with_key(item, "Attributes") if next_url: diff --git a/python-lib/osisoft_plugin_common.py b/python-lib/osisoft_plugin_common.py index e7a1cb2..9c62767 100644 --- a/python-lib/osisoft_plugin_common.py +++ b/python-lib/osisoft_plugin_common.py @@ -660,15 +660,18 @@ def __init__(self, root_tree=None): if root_tree: self._ingest(root_tree) - def _ingest(self, root_tree): + def _ingest(self, root_tree, parent_path=None): + parent_path = parent_path or [] if isinstance(root_tree, list): for item in root_tree: + if not parent_path: + path = item.get("path", "") + parent_path = path.split("\\")[2:][0:2] item_children = item.pop("children", []) - if item_children: - self._ingest(item_children) + title = item.get("title") + self._ingest(item_children, parent_path=parent_path + [title]) path = item.get("path", "") - path_tokens = path.replace("|", "\\").split("\\")[2:] - self.put(path_tokens, item) + self.put(parent_path + [title], item) def put(self, path, data): if isinstance(path, list): @@ -741,10 +744,10 @@ def recursive_tree_rebuild(dictionary, records, counter=None): children = recursive_tree_rebuild(sub_dictionary, records, counter + 1) else: children = [] - context["id"] = str(counter) + # context["id"] = str(counter) context["title"] = key context["expanded"] = True - context["checked"] = False + # context["checked"] = False context["children"] = children output.append(context) return output diff --git a/resource/browse_af_tree.py b/resource/browse_af_tree.py index fbe4b66..dbcbf26 100644 --- a/resource/browse_af_tree.py +++ b/resource/browse_af_tree.py @@ -58,12 +58,29 @@ def do(payload, config, plugin_config, inputs): element_name = config.get("element_name") attribute_name = config.get("attribute_name") root_tree = payload.get("root_tree") + root_tree = shorten_tree(root_tree) attributes = [] # https://dku-qa-osi.francecentral.cloudapp.azure.com/piwebapi/assetdatabases/F1RD3VEt1yTvt0ip6-a5yeEVsgbMcrwu_Je0qg9btcZIvPswT1NJU09GVC1QSS1TRVJWXFdFTEw database_webid = database_name.split("/")[-1] + # element_query_keys = { + # "element_name": "Name:'{}'", + # "search_root_path": "Root:'{}'", + # "element_template": "Template:'{}'", + # "element_type": "Type:'{}'", + # "element_category": "CategoryName:'{}'" + # } + # attribute_query_keys = { + # "attribute_name": "Name:'{}'", + # "attribute_category": "CategoryName:'{}'", + # "attribute_value_type": "Type:'{}'" + # } for attribute in client.search_attributes( - database_webid, attribute_name=attribute_name, element_name=element_name): + database_webid, + attribute_name=attribute_name, + element_name=element_name + ): # print("ALX:attribute={}".format(attribute)) + attribute["checked"] = True attributes.append(attribute) rebuilt_tree = rebuild_tree(client, attributes, root_tree) return {"choices": rebuilt_tree} @@ -189,3 +206,14 @@ def path_to_list(path): if not path: return [] return path.split('|')[0].split('\\')[2:], (path.split('|')[1:]) + + +def shorten_tree(tree): + if isinstance(tree, list): + for node in tree: + if "expanded" in node: + # node.pop("expanded", None) + node["expanded"] = False + if "children" in node: + shorten_tree(node.get("children", [])) + return tree From 29963d4ab0c93e45ffbbc33d1c4706a97982245f Mon Sep 17 00:00:00 2001 From: Alex Bourret Date: Tue, 6 Jan 2026 11:18:16 +0100 Subject: [PATCH 04/17] display duplicated attributes --- python-lib/osisoft_client.py | 2 ++ resource/browse_af_tree.py | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/python-lib/osisoft_client.py b/python-lib/osisoft_client.py index ff75709..ce0e85c 100644 --- a/python-lib/osisoft_client.py +++ b/python-lib/osisoft_client.py @@ -733,6 +733,8 @@ def search_attributes(self, database_webid, **kwargs): "query": query, "databaseWebId": database_webid } + if "search_associations" in kwargs: + params["associations"] = kwargs.get("search_associations") json_response = self.get(url=search_attributes_base_url, headers=headers, params=params) if OSIsoftConstants.DKU_ERROR_KEY in json_response: yield json_response diff --git a/resource/browse_af_tree.py b/resource/browse_af_tree.py index dbcbf26..6d8d8d5 100644 --- a/resource/browse_af_tree.py +++ b/resource/browse_af_tree.py @@ -77,11 +77,13 @@ def do(payload, config, plugin_config, inputs): for attribute in client.search_attributes( database_webid, attribute_name=attribute_name, - element_name=element_name + element_name=element_name, + search_associations="Paths" ): # print("ALX:attribute={}".format(attribute)) attribute["checked"] = True attributes.append(attribute) + attributes = duplicate_linked_attributes(attributes) rebuilt_tree = rebuild_tree(client, attributes, root_tree) return {"choices": rebuilt_tree} @@ -217,3 +219,14 @@ def shorten_tree(tree): if "children" in node: shorten_tree(node.get("children", [])) return tree + + +def duplicate_linked_attributes(attributes): + duplicated_attributes = [] + for attribute in attributes: + paths = attribute.pop("Paths", [attribute.get("Path")]) + for path in paths: + this_attribute = attribute.copy() + this_attribute["Path"] = path + duplicated_attributes.append(this_attribute) + return duplicated_attributes From 700eec6ea3564c749ef85659fcb4b4772b360172 Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 7 Jan 2026 15:08:33 +0100 Subject: [PATCH 05/17] Edited files --- js/pi-system_treecontroller.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/js/pi-system_treecontroller.js b/js/pi-system_treecontroller.js index f95512e..6cba4b1 100644 --- a/js/pi-system_treecontroller.js +++ b/js/pi-system_treecontroller.js @@ -103,11 +103,15 @@ app.controller('AfExplorerFormCtrl', [ }; $scope.initializeTree = function(){ + console.log("initialization: "); + console.log($scope.config.treeData); + if (!$scope.config.treeData || $scope.config.treeData===[]){ $scope.callPythonDo({method: "get_children_from_db", parent: $scope.config.database_name}).then(function(data){ console.log("ALX:data2=" + JSON.stringify(data)); TreeDataService.setTreeData(data.choices); - $scope.treeData = TreeDataService.getTreeData(); + $scope.config.treeData = TreeDataService.getTreeData(); }); + } }; $scope.getChildrenFromDB = function(item){ @@ -138,7 +142,7 @@ app.controller('AfExplorerFormCtrl', [ $scope.doSearch = function(element_name, attribute_name){ $scope.callPythonDo({method: "do_search", element_name: element_name, attribute_name: attribute_name, root_tree: $scope.treeData}).then( function(data){ - $scope.treeData = data.choices; + $scope.config.treeData = data.choices; } ); }; From 3c39c7d16232e6c04a61fb34dc5c38414dd60a62 Mon Sep 17 00:00:00 2001 From: admin Date: Wed, 7 Jan 2026 15:09:18 +0100 Subject: [PATCH 06/17] Edited files --- js/pi-system_treecontroller.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/pi-system_treecontroller.js b/js/pi-system_treecontroller.js index 6cba4b1..79f85ad 100644 --- a/js/pi-system_treecontroller.js +++ b/js/pi-system_treecontroller.js @@ -142,7 +142,9 @@ app.controller('AfExplorerFormCtrl', [ $scope.doSearch = function(element_name, attribute_name){ $scope.callPythonDo({method: "do_search", element_name: element_name, attribute_name: attribute_name, root_tree: $scope.treeData}).then( function(data){ - $scope.config.treeData = data.choices; + //$scope.config.treeData = data.choices; + TreeDataService.setTreeData(data.choices); + $scope.config.treeData = TreeDataService.getTreeData(); } ); }; From c6669e56a69b807d7d4b086f5e66a33390999a29 Mon Sep 17 00:00:00 2001 From: JaneBellaiche Date: Wed, 7 Jan 2026 15:18:10 +0100 Subject: [PATCH 07/17] save and restore state --- resource/browse_af_tree.py | 2 ++ resource/pi-system_af-explorer.html | 16 +++++++++++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/resource/browse_af_tree.py b/resource/browse_af_tree.py index 6d8d8d5..9835fd9 100644 --- a/resource/browse_af_tree.py +++ b/resource/browse_af_tree.py @@ -111,6 +111,8 @@ def do(payload, config, plugin_config, inputs): return build_select_choices(choices) else: return build_select_choices() + if parameter_name == "treeData": + return {"choices": config.get("treeData")} return build_select_choices() diff --git a/resource/pi-system_af-explorer.html b/resource/pi-system_af-explorer.html index 6506789..5687bb1 100644 --- a/resource/pi-system_af-explorer.html +++ b/resource/pi-system_af-explorer.html @@ -45,10 +45,20 @@ - +
      + +
      + +
      + +
      -
        -
      • +
          +
        From 988e75fc7f88f967f4247499db801bb6a8e0df07 Mon Sep 17 00:00:00 2001 From: JaneBellaiche Date: Thu, 8 Jan 2026 10:19:38 +0100 Subject: [PATCH 08/17] extend UI width --- resource/pi-system_af-explorer.css | 3 +++ resource/pi-system_af-explorer.html | 5 +++-- 2 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 resource/pi-system_af-explorer.css diff --git a/resource/pi-system_af-explorer.css b/resource/pi-system_af-explorer.css new file mode 100644 index 0000000..7652913 --- /dev/null +++ b/resource/pi-system_af-explorer.css @@ -0,0 +1,3 @@ +.fh.w800.oa { + width: 100% !important; +} \ No newline at end of file diff --git a/resource/pi-system_af-explorer.html b/resource/pi-system_af-explorer.html index 5687bb1..689ec48 100644 --- a/resource/pi-system_af-explorer.html +++ b/resource/pi-system_af-explorer.html @@ -1,3 +1,4 @@ +
        @@ -45,8 +46,8 @@ -
        - +
        +
        + {{attribute.title}} + {{attribute.path}} + +
        From c3c79d2cef4f5c0cfbb2043245e107bd6616dd31 Mon Sep 17 00:00:00 2001 From: JaneBellaiche Date: Wed, 21 Jan 2026 12:42:44 +0100 Subject: [PATCH 15/17] typo --- js/pi-system_treecontroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/pi-system_treecontroller.js b/js/pi-system_treecontroller.js index 40a5615..105847a 100644 --- a/js/pi-system_treecontroller.js +++ b/js/pi-system_treecontroller.js @@ -138,7 +138,7 @@ app.controller('AfExplorerFormCtrl', [ }; -$scope.newDisplayAttributes = function(node) { +$scope.displayAttributes = function(node) { if (!node.children || node.children.length === 0) { $scope.getChildrenFromDB(node).then(newNode => { From 0af06a73cf2379fd4e2adee35f91fe156db2615e Mon Sep 17 00:00:00 2001 From: JaneBellaiche Date: Wed, 21 Jan 2026 12:55:19 +0100 Subject: [PATCH 16/17] fix elements highlight --- js/pi-system_treecontroller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/pi-system_treecontroller.js b/js/pi-system_treecontroller.js index 105847a..09c97d8 100644 --- a/js/pi-system_treecontroller.js +++ b/js/pi-system_treecontroller.js @@ -224,7 +224,7 @@ app.directive('treeNode', function() { } return scope.$parent.config.attributeList.some(child => { - const expected = node.title + "|" + child.name; + const expected = node.title + "|" + child.title; return child.path.endsWith(expected); }); }; From 7d83faaa6a5acb7b5b0f82f9f0cb5ea47a543e4d Mon Sep 17 00:00:00 2001 From: JaneBellaiche Date: Wed, 21 Jan 2026 13:59:14 +0100 Subject: [PATCH 17/17] little bit of UI --- resource/pi-system_af-explorer.css | 11 +++++- resource/pi-system_af-explorer.html | 59 +++++++++++++++++++---------- 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/resource/pi-system_af-explorer.css b/resource/pi-system_af-explorer.css index 10bdd3c..575492b 100644 --- a/resource/pi-system_af-explorer.css +++ b/resource/pi-system_af-explorer.css @@ -4,4 +4,13 @@ .tree-node__label--clickable{ background-color: yellow; -} \ No newline at end of file +} +.pi-system-explorer__main { + display: grid; + grid-template-columns: 500px auto; + column-gap: 50px; + margin-top: 20px; +} +.pi-system-explorer__tree-view, .pi-system-explorer__center-view { + border: 1px solid #ccc; padding: 10px; border-radius: 5px; +} diff --git a/resource/pi-system_af-explorer.html b/resource/pi-system_af-explorer.html index 2db29fc..d331c88 100644 --- a/resource/pi-system_af-explorer.html +++ b/resource/pi-system_af-explorer.html @@ -39,13 +39,6 @@
        - - - - -
        @@ -57,20 +50,44 @@
        -
        -
          -
        • - -
        • -
        -
        -
        - - - - - -
        {{attribute.title}}{{attribute.path}}
        +
        +
        +
        Elements
        +
        + + +
        +
          +
        • + +
        • +
        +
        + + +
        +
        Attributes
        +
        + + +
        + + + + + + +
        + + {{attribute.title}}{{attribute.path}}
        +
        +
        + +