|
|
@@ -0,0 +1,319 @@
|
|
|
+# TODO Lot of hardcoded shizzle
|
|
|
+import logging
|
|
|
+from dataclasses import dataclass, field
|
|
|
+from itertools import groupby
|
|
|
+from typing import Optional, List
|
|
|
+from urllib.error import URLError
|
|
|
+
|
|
|
+import arklog
|
|
|
+import dearpygui.dearpygui as dpg
|
|
|
+import dearpygui.demo as demo
|
|
|
+
|
|
|
+from graph_exploring_tool import query
|
|
|
+
|
|
|
+arklog.set_config_logging()
|
|
|
+
|
|
|
+example_prefix = """PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
|
|
|
+PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
|
|
|
+PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
|
|
+PREFIX dtf: <https://ontology.rys.app/dt/function/>
|
|
|
+PREFIX owl: <http://www.w3.org/2002/07/owl#>
|
|
|
+"""
|
|
|
+example_query = """SELECT ?outlier ?outlier_relation ?outlier_value WHERE {
|
|
|
+ SERVICE <http://127.0.0.1:8000/> {
|
|
|
+ SELECT ?outlier ?outlier_relation ?outlier_value WHERE {
|
|
|
+ BIND(dtf:outlier("rotation.csv", "2", "<http://ua.be/drivetrain/description/artifacts/artifacts#drivetrain-sensor-data-v1>") AS ?outlier)
|
|
|
+ }
|
|
|
+ }
|
|
|
+}"""
|
|
|
+
|
|
|
+
|
|
|
+@dataclass(init=True, repr=True, order=False, frozen=True)
|
|
|
+class Replacement:
|
|
|
+ origin: str
|
|
|
+ description: str
|
|
|
+
|
|
|
+@dataclass(init=True, repr=True, order=False, frozen=True)
|
|
|
+class QueryTemplate:
|
|
|
+ group: str
|
|
|
+ name: str
|
|
|
+ prefix: str
|
|
|
+ query: str
|
|
|
+ description: str
|
|
|
+ visual_support: bool = False
|
|
|
+ replacements: List[Replacement] = field(default_factory=list)
|
|
|
+
|
|
|
+
|
|
|
+# TODO Grab from config files?
|
|
|
+query_palette = (
|
|
|
+ QueryTemplate(group="Types", name="Find Types", prefix=query.prefixes, query=query.get_types(), description="Find all the types stored in the knowledge graph.", visual_support=True),
|
|
|
+ QueryTemplate(group="Service", name="Example Function", prefix=query.prefixes, query=query.get_example(), description="Example query calling the example function.", visual_support=True),
|
|
|
+ QueryTemplate(group="Service", name="Outlier Function", prefix=query.prefixes, query=query.get_outlier(), description="Find an outlier in a tabular format.", visual_support=True, replacements=[
|
|
|
+ Replacement(origin="http://127.0.0.1:8000/", description="Which service do you want to use?"),
|
|
|
+ Replacement(origin="rotation.csv", description="In which file is the outlier located?"),
|
|
|
+ Replacement(origin="2222", description="In which column?"),
|
|
|
+ Replacement(origin="<http://ua.be/drivetrain/description/artifacts/artifacts#drivetrain-sensor-data-v1>", description="In which artifact?"),
|
|
|
+ ]),
|
|
|
+ QueryTemplate(group="Traceability", name="Find Versions", prefix=query.prefixes, query=query.get_versions(), description="Find all the older versions of an artifact.", visual_support=True, replacements=[Replacement(origin="art:drivetrain-sensor-data-v2", description="For which artifact are you looking for older versions?")]),
|
|
|
+ QueryTemplate(group="Individuals", name="Find Properties", prefix=query.prefixes, query=query.get_properties(), description="Find all possible properties of an individual.", visual_support=True, replacements=[Replacement(origin="ftg:Formalism", description="What class of individual are you looking for?")]),
|
|
|
+ QueryTemplate(group="Individuals", name="Find Individuals", prefix=query.prefixes, query=query.get_individuals(), description="Find all individuals.", visual_support=True, replacements=[Replacement(origin="ftg:Formalism", description="What class of individual are you looking for?"), Replacement(origin="base:hasName", description="What is the relation you are looking for?")]),
|
|
|
+ QueryTemplate(group="Traceability", name="Find Class Property", prefix=query.prefixes, query=query.get_class_property(), description="Find all the properties for a class.", visual_support=True, replacements=[Replacement(origin="base:ArtifactType", description="For which class do you want to find properties?")]),
|
|
|
+ QueryTemplate(group="Traceability", name="Match Artifact Formalism", prefix=query.prefixes, query=query.get_art_formalism(), description="Find the formalism corresponding to an artifact.", visual_support=True, replacements=[
|
|
|
+ Replacement(origin="art:torque-profile", description="For which artifact do you want to find formalism?")
|
|
|
+ ]),
|
|
|
+ QueryTemplate(group="Traceability", name="Find PM Relation", prefix=query.prefixes, query=query.get_pm(), description="Find the PM.", visual_support=True, replacements=[
|
|
|
+ Replacement(origin="ftg:Transformation", description="This should be a transformation?"),
|
|
|
+ Replacement(origin="ftg:outputs", description="This should be a relation from the transformation?"),
|
|
|
+ Replacement(origin="ftg:Formalism", description="The class of the formalism?"),
|
|
|
+ Replacement(origin="base:hasName", description="Relation from formalism?"),
|
|
|
+ ]),
|
|
|
+)
|
|
|
+
|
|
|
+
|
|
|
+def _config(sender, keyword, user_data):
|
|
|
+
|
|
|
+ widget_type = dpg.get_item_type(sender)
|
|
|
+ items = user_data
|
|
|
+
|
|
|
+ if widget_type == "mvAppItemType::mvRadioButton":
|
|
|
+ value = True
|
|
|
+
|
|
|
+ else:
|
|
|
+ keyword = dpg.get_item_label(sender)
|
|
|
+ value = dpg.get_value(sender)
|
|
|
+
|
|
|
+ if isinstance(user_data, list):
|
|
|
+ for item in items:
|
|
|
+ dpg.configure_item(item, **{keyword: value})
|
|
|
+ else:
|
|
|
+ dpg.configure_item(items, **{keyword: value})
|
|
|
+
|
|
|
+def add_main_menu():
|
|
|
+ with dpg.menu_bar():
|
|
|
+ with dpg.menu(label="Menu"):
|
|
|
+ dpg.add_menu_item(label="New")
|
|
|
+ dpg.add_menu_item(label="Open")
|
|
|
+ with dpg.menu(label="Open Recent"):
|
|
|
+ dpg.add_menu_item(label="patty.h")
|
|
|
+ dpg.add_menu_item(label="nick.py")
|
|
|
+ dpg.add_menu_item(label="Save")
|
|
|
+ dpg.add_menu_item(label="Exit")
|
|
|
+
|
|
|
+
|
|
|
+# TODO Add tab for prefixes
|
|
|
+def create_query_palette():
|
|
|
+ demo_layout_child = dpg.generate_uuid()
|
|
|
+ with dpg.child_window(tag=demo_layout_child, label="Query Palette", width=220, menubar=True):
|
|
|
+ with dpg.menu_bar():
|
|
|
+ dpg.add_menu(label="Query Palette", enabled=False)
|
|
|
+ dpg.add_input_text(label="Filter", callback=lambda s, a: dpg.set_value("__query_filter", a))
|
|
|
+ with dpg.filter_set(tag="__query_filter"):
|
|
|
+ # TODO Populate from onto/base
|
|
|
+ # TODO Bug fix this; actually figure out groups
|
|
|
+ # TODO Fix filtering
|
|
|
+ grouped_query_palette = groupby(sorted(query_palette, key=lambda palette_query: palette_query.group), key=lambda palette_query: palette_query.group)
|
|
|
+ for query_template_group, query_template_queries in grouped_query_palette:
|
|
|
+ with dpg.tree_node(label=query_template_group, tag=query_template_group, default_open=True, filter_key=""):
|
|
|
+ full_filter = ""
|
|
|
+ for query_template in query_template_queries:
|
|
|
+ query_button = dpg.add_button(label=query_template.name, filter_key=query_template.name, callback=_query_palette_click, user_data=query_template)
|
|
|
+ with dpg.tooltip(dpg.last_item()):
|
|
|
+ dpg.add_text(query_template.description)
|
|
|
+ full_filter =f"{full_filter},{query_template.name}"
|
|
|
+ # TODO Make theme-ing more generic
|
|
|
+ with dpg.theme() as item_theme:
|
|
|
+ with dpg.theme_component(dpg.mvAll):
|
|
|
+ dpg.add_theme_color(dpg.mvThemeCol_Text, (200, 200, 100), category=dpg.mvThemeCat_Core)
|
|
|
+ dpg.add_theme_style(dpg.mvStyleVar_FrameRounding, 0, category=dpg.mvThemeCat_Core)
|
|
|
+ if query_template.visual_support:
|
|
|
+ dpg.bind_item_theme(query_button, item_theme)
|
|
|
+ dpg.configure_item(query_template_group, filter_key=full_filter)
|
|
|
+
|
|
|
+def create_query_options(endpoint: str):
|
|
|
+ with dpg.group(horizontal=True):
|
|
|
+ dpg.add_combo(("Visual", "Textual"), default_value="Visual", callback=_mode_select, width=100, tag="__editor_selector")
|
|
|
+ dpg.add_input_text(default_value=endpoint, width=300, enabled=False, tag="__endpoint_input")
|
|
|
+ dpg.add_checkbox(label="debug", callback=_config)
|
|
|
+ dpg.add_checkbox(label="annotate", default_value=False, callback=_config) # TODO Actually make this annotate the result data with its type
|
|
|
+
|
|
|
+def create_query_editor_visual(show: bool = False):
|
|
|
+ with dpg.child_window(autosize_x=True, height=500, menubar=True, show=show, tag="__query_editor_visual"):
|
|
|
+ with dpg.menu_bar():
|
|
|
+ dpg.add_menu(label="Visual Query Editor", enabled=False)
|
|
|
+ with dpg.tab_bar():
|
|
|
+ with dpg.tab(label="Query", tag="__query_tab"):
|
|
|
+ dpg.add_input_text(default_value=example_query, callback=_log, multiline=True, on_enter=True, height=300, width=-1, tag="__query_editor_visual_input")
|
|
|
+ with dpg.tab(label="Prefix", tag="__prefix_tab"):
|
|
|
+ dpg.add_input_text(default_value=example_prefix, callback=_log, multiline=True, on_enter=True, height=300, width=-1, tag="__prefix_editor_visual_input")
|
|
|
+ with dpg.group(tag="__visual_editor_fields"):
|
|
|
+ pass
|
|
|
+ with dpg.group(horizontal=True):
|
|
|
+ dpg.add_button(label="Query", callback=_perform_query)
|
|
|
+ dpg.add_button(label="Save", callback=_select_directory) # TODO
|
|
|
+ dpg.add_button(label="Load") # TODO
|
|
|
+
|
|
|
+def create_query_editor_textual(show: bool = False):
|
|
|
+ with dpg.child_window(autosize_x=True, height=500, menubar=True, show=show, tag="__query_editor_textual"):
|
|
|
+ with dpg.menu_bar():
|
|
|
+ dpg.add_menu(label="Textual Query Editor", enabled=False)
|
|
|
+ with dpg.tab_bar():
|
|
|
+ with dpg.tab(label="Query", tag="__query_textual_tab"):
|
|
|
+ dpg.add_input_text(default_value=example_query, callback=_log, multiline=True, on_enter=True, height=300, width=-1, tag="__query_editor_textual_input")
|
|
|
+ with dpg.tab(label="Prefix", tag="__prefix_textual_tab"):
|
|
|
+ dpg.add_input_text(default_value=example_prefix, callback=_log, multiline=True, on_enter=True, height=300, width=-1, tag="__prefix_editor_textual_input")
|
|
|
+ with dpg.group(horizontal=True):
|
|
|
+ dpg.add_button(label="Query", callback=_perform_query)
|
|
|
+ dpg.add_button(label="Save", callback=_select_directory) # TODO
|
|
|
+ dpg.add_button(label="Load") # TODO
|
|
|
+
|
|
|
+# TODO Not at the bottom because fok you
|
|
|
+def create_status_console():
|
|
|
+ with dpg.child_window(autosize_x=True, height=35):
|
|
|
+ with dpg.group(horizontal=True):
|
|
|
+ dpg.add_text(default_value="Ready.", tag="__status_console")
|
|
|
+
|
|
|
+def set_copy(s, a):
|
|
|
+ dpg.set_value(s, shorten(a))
|
|
|
+ i = dpg.get_item_label(s)
|
|
|
+ dpg.set_value(f"__copy_{i}", shorten(a))
|
|
|
+ dpg.configure_item(f"__copy_drag_payload_{i}", drag_data=shorten(a))
|
|
|
+ dpg.set_value(f"__copy_drag_{i}", shorten(a))
|
|
|
+
|
|
|
+def create_query_results():
|
|
|
+ with dpg.child_window(autosize_x=True, autosize_y=True, menubar=True, tag="__query_results_window"):
|
|
|
+ with dpg.menu_bar():
|
|
|
+ dpg.add_menu(label="Results", enabled=False)
|
|
|
+ # dpg.add_input_text(label="Filter", tag="__result_filter_input", callback=lambda s, a: dpg.set_value("__result_filter", a))
|
|
|
+ with dpg.tab_bar():
|
|
|
+ with dpg.tab(label="Query Result", tag="__query_result_tab"):
|
|
|
+ with dpg.table(header_row=True, policy=dpg.mvTable_SizingFixedFit, row_background=True, reorderable=True,
|
|
|
+ resizable=True, no_host_extendX=False, hideable=True,
|
|
|
+ borders_innerV=True, delay_search=True, borders_outerV=True, borders_innerH=True,
|
|
|
+ borders_outerH=True, tag="__result_table"):
|
|
|
+ pass
|
|
|
+ with dpg.tab(label="Saved"):
|
|
|
+ with dpg.group(horizontal=True):
|
|
|
+ with dpg.group():
|
|
|
+ for i in range(0, 10):
|
|
|
+ dpg.add_input_text(label=f"{i}", payload_type="string", width=500, drop_callback=lambda s, a: set_copy(s,a))
|
|
|
+ with dpg.group():
|
|
|
+ for i in range(0, 10):
|
|
|
+ dpg.add_text(tag=f"__copy_{i}", default_value="Empty")
|
|
|
+ with dpg.drag_payload(parent=dpg.last_item(), drag_data="Empty", payload_type="string", tag=f"__copy_drag_payload_{i}"):
|
|
|
+ dpg.add_text(tag=f"__copy_drag_{i}", default_value="Empty")
|
|
|
+
|
|
|
+ # dpg.add_text("TODO") # TODO Add ability to save some results for reuse in templates
|
|
|
+ with dpg.tab(label="Debug"):
|
|
|
+ dpg.add_text("This is the debug tab!")
|
|
|
+
|
|
|
+
|
|
|
+def _log(sender: int, app_data: str, user_data: Optional[dict]):
|
|
|
+ logging.debug(f"sender: {sender}, \t app_data: {app_data}, \t user_data: {user_data}")
|
|
|
+
|
|
|
+def _perform_text_query(sender: int, app_data: str, user_data: Optional[dict]):
|
|
|
+ logging.debug(f"sender: {sender}, \t app_data: {app_data}, \t user_data: {user_data}")
|
|
|
+
|
|
|
+def _mode_select(sender: int, mode: str, user_data: Optional[dict]):
|
|
|
+ mode = mode.lower().strip()
|
|
|
+ visual = mode == "visual"
|
|
|
+ # TODO When showing visual we need to fix the custom fields
|
|
|
+ dpg.configure_item("__query_editor_visual", show=visual)
|
|
|
+ dpg.configure_item("__query_editor_textual", show=not visual)
|
|
|
+
|
|
|
+def shorten(identifier: str) -> str:
|
|
|
+ for key, value in query.reverse_prefix.items():
|
|
|
+ identifier = identifier.replace(key, f"{value}:")
|
|
|
+ return identifier
|
|
|
+
|
|
|
+def _query_palette_click(sender: int, mode: str, user_data: Optional[QueryTemplate]):
|
|
|
+ mode = dpg.get_value("__editor_selector").lower().strip()
|
|
|
+ if mode == "visual" and not user_data.visual_support:
|
|
|
+ logging.warning(f"Visual mode for template '{user_data.name}' not implemented yet!")
|
|
|
+ dpg.set_value("__status_console", f"Visual mode for template '{user_data.name}' not implemented yet!")
|
|
|
+ return
|
|
|
+ dpg.set_value("__status_console", f"Using {mode} template '{user_data.name}'.")
|
|
|
+ dpg.set_value(f"__prefix_editor_{mode}_input", user_data.prefix)
|
|
|
+ dpg.set_value(f"__query_editor_{mode}_input", user_data.query)
|
|
|
+ dpg.delete_item("__visual_editor_fields", children_only=True)
|
|
|
+ if mode == "visual" and user_data.visual_support:
|
|
|
+ # TODO set visual components
|
|
|
+ for replacement in user_data.replacements:
|
|
|
+ dpg.add_input_text(label=f"{replacement.description}",default_value=replacement.origin, payload_type="string", width=500, parent="__visual_editor_fields", drop_callback=lambda s, a: dpg.set_value(s, shorten(a)), tag=f"__{replacement.origin}", user_data=replacement)
|
|
|
+
|
|
|
+def _perform_query(sender: int, mode: str, user_data: Optional[dict]):
|
|
|
+ mode = dpg.get_value("__editor_selector").lower().strip()
|
|
|
+ prefix_text = dpg.get_value(f"__prefix_editor_{mode}_input")
|
|
|
+ query_text = dpg.get_value(f"__query_editor_{mode}_input")
|
|
|
+ endpoint = dpg.get_value("__endpoint_input")
|
|
|
+ try:
|
|
|
+ if mode=="visual":
|
|
|
+ for replacement_field in dpg.get_item_children("__visual_editor_fields", 1):
|
|
|
+ value = dpg.get_value(replacement_field)
|
|
|
+ origin = dpg.get_item_user_data(replacement_field).origin
|
|
|
+ query_text = query_text.replace(origin, value)
|
|
|
+ query_result = query.perform_query(endpoint, prefix_text + "\n" + query_text)
|
|
|
+ except URLError as e:
|
|
|
+ logging.error(f"Connection to '{endpoint}' failed.")
|
|
|
+ dpg.set_value(f"__status_console", f"Connection to '{endpoint}' failed.")
|
|
|
+ return
|
|
|
+ result_items = query_result["results"]["bindings"]
|
|
|
+ if not result_items:
|
|
|
+ dpg.delete_item("__result_table", children_only=True) # TODO Clear table https://github.com/hoffstadt/DearPyGui/issues/1350
|
|
|
+ logging.debug(f"No results returned.")
|
|
|
+ dpg.set_value(f"__status_console", "No results.")
|
|
|
+ return
|
|
|
+ dpg.delete_item("__result_table", children_only=True)
|
|
|
+ dpg.set_value(f"__status_console", "Query successful.")
|
|
|
+ columns = result_items[0].keys()
|
|
|
+ for column in columns:
|
|
|
+ dpg.add_table_column(label=column, width_fixed=True, parent="__result_table")
|
|
|
+ # dpg.add_table_column(label="CCC", width_stretch=True, init_width_or_weight=0.0)
|
|
|
+
|
|
|
+ # TODO This is as brittle as my ego, needs a fix asap
|
|
|
+ # TODO Fix filter
|
|
|
+ # with dpg.filter_set(tag="__result_filter", parent="__query_result_tab"):
|
|
|
+ for result in result_items:
|
|
|
+ with dpg.table_row(parent=f"__result_table"):
|
|
|
+ for key, value in result.items():
|
|
|
+ shortened = shorten(f"{value.get('value')}")
|
|
|
+ dpg.add_text(shortened, filter_key=shortened)
|
|
|
+ with dpg.drag_payload(parent=dpg.last_item(), drag_data=shortened, payload_type="string"):
|
|
|
+ dpg.add_text(shortened, filter_key=shortened)
|
|
|
+
|
|
|
+
|
|
|
+def _select_directory(sender: int, mode: str, user_data: Optional[dict]):
|
|
|
+ with dpg.file_dialog(directory_selector=True, show=True, callback=_process_directory):
|
|
|
+ dpg.add_file_extension(".*")
|
|
|
+
|
|
|
+def _process_directory(sender: int, app_data: str, user_data: Optional[dict]):
|
|
|
+ directory = app_data["file_path_name"]
|
|
|
+ # files = listdir(directory)
|
|
|
+ # dpg.configure_item("files_listbox", items=files)
|
|
|
+ # dpg.set_value("file_text", directory)
|
|
|
+
|
|
|
+def _select_file(sender: int, app_data: str, user_data: Optional[dict]):
|
|
|
+ selected_file = app_data
|
|
|
+ cwd = dpg.get_value("file_text")
|
|
|
+ selected_file = cwd + "/" + selected_file
|
|
|
+ dpg.set_value("file_info_n", "file :" + selected_file)
|
|
|
+
|
|
|
+def interface(width=1200, height=900, endpoint: str = "localhost"):
|
|
|
+ endpoint_middleware = "localhost:8000" # TODO Grab this from onto
|
|
|
+ dpg.create_context()
|
|
|
+ dpg.create_viewport(title="Graph Exploring Tool", width=width, height=height)
|
|
|
+ with dpg.window(tag="primary", label="Graph Exploring Tool", menubar=True):
|
|
|
+ add_main_menu()
|
|
|
+ with dpg.group(horizontal=True, label="Main"):
|
|
|
+ create_query_palette()
|
|
|
+ with dpg.group(horizontal=False):
|
|
|
+ create_query_options(endpoint)
|
|
|
+ create_query_editor_visual(show=True)
|
|
|
+ create_query_editor_textual(show=False)
|
|
|
+ create_status_console()
|
|
|
+ create_query_results()
|
|
|
+ # demo.show_demo()
|
|
|
+ dpg.setup_dearpygui()
|
|
|
+ dpg.show_viewport()
|
|
|
+ dpg.set_primary_window("primary", True)
|
|
|
+ dpg.start_dearpygui()
|
|
|
+ dpg.destroy_context()
|