Skip to content

Commit

Permalink
Bugfixes for openCypher result widgets (#286)
Browse files Browse the repository at this point in the history
* OC widgets rendering bugfixes

* remove debug

* Update Changelog

Co-authored-by: Michael Chin <[email protected]>
  • Loading branch information
michaelnchin and michaelnchin authored May 17, 2022
1 parent 11d00e1 commit 45c3fc6
Show file tree
Hide file tree
Showing 4 changed files with 131 additions and 43 deletions.
2 changes: 2 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Starting with v1.31.6, this file will contain a record of major features and upd
- Added JupyterLab startup script to auto-load magics extensions ([Link to PR](https://github.com/aws/graph-notebook/pull/277))
- Added includeWaiting option to %oc_status, fix same for %gremlin_status ([Link to PR](https://github.com/aws/graph-notebook/pull/272))
- Added `--store-to` option to %status ([Link to PR](https://github.com/aws/graph-notebook/pull/278))
- Fixed handling of empty nodes returned from openCypher `DELETE` queries ([Link to PR](https://github.com/aws/graph-notebook/pull/286))
- Fixed rendering of openCypher widgets for empty result sets ([Link to PR](https://github.com/aws/graph-notebook/pull/286))
- Fixed graph search overriding physics setting ([Link to PR](https://github.com/aws/graph-notebook/pull/282))
- Fixed browser-specific bug in results pagination options menu ([Link to PR](https://github.com/aws/graph-notebook/pull/290))
- Removed `requests-aws4auth` requirement ([Link to PR](https://github.com/aws/graph-notebook/pull/291))
Expand Down
78 changes: 37 additions & 41 deletions src/graph_notebook/magics/graph_magic.py
Original file line number Diff line number Diff line change
Expand Up @@ -1741,8 +1741,8 @@ def handle_opencypher_query(self, line, cell, local_ns):
tab = widgets.Tab()
titles = []
children = []
force_graph_output = None
explain_html = ""
first_tab_output = widgets.Output(layout=oc_layout)
children.append(first_tab_output)

if args.mode == 'explain':
query_start = time.time() * 1000 # time.time() returns time in seconds w/high precision; x1000 to get in ms
Expand All @@ -1754,9 +1754,10 @@ def handle_opencypher_query(self, line, cell, local_ns):
if not args.silent:
oc_metadata = build_opencypher_metadata_from_query(query_type='explain', results=None, res=res,
query_time=query_time)
titles.append('Explain')
explain_bytes = explain.encode('utf-8')
base64_str = base64.b64encode(explain_bytes).decode('utf-8')
explain_html = opencypher_explain_template.render(table=explain, link=f"data:text/html;base64,{base64_str}")
first_tab_html = opencypher_explain_template.render(table=explain, link=f"data:text/html;base64,{base64_str}")
elif args.mode == 'query':
query_start = time.time() * 1000 # time.time() returns time in seconds w/high precision; x1000 to get in ms
oc_http = self.client.opencypher_http(cell)
Expand All @@ -1766,6 +1767,15 @@ def handle_opencypher_query(self, line, cell, local_ns):
if not args.silent:
oc_metadata = build_opencypher_metadata_from_query(query_type='query', results=res,
query_time=query_time)
first_tab_html = ""
rows_and_columns = opencypher_get_rows_and_columns(res, False)
if rows_and_columns:
titles.append('Console')
table_id = f"table-{str(uuid.uuid4())[:8]}"
visible_results = results_per_page_check(args.results_per_page)
first_tab_html = opencypher_table_template.render(columns=rows_and_columns['columns'],
rows=rows_and_columns['rows'], guid=table_id,
amount=visible_results)
try:
gn = OCNetwork(group_by_property=args.group_by, display_property=args.display_property,
group_by_raw=args.group_by_raw,
Expand All @@ -1783,51 +1793,32 @@ def handle_opencypher_query(self, line, cell, local_ns):
= args.stop_physics
self.graph_notebook_vis_options['physics']['simulationDuration'] = args.simulation_duration
force_graph_output = Force(network=gn, options=self.graph_notebook_vis_options)
titles.append('Graph')
children.append(force_graph_output)
except (TypeError, ValueError) as network_creation_error:
logger.debug(f'Unable to create network from result. Skipping from result set: {res}')
logger.debug(f'Error: {network_creation_error}')

elif args.mode == 'bolt':
query_start = time.time() * 1000
res = self.client.opencyper_bolt(cell)
query_time = time.time() * 1000 - query_start
if not args.silent:
oc_metadata = build_opencypher_metadata_from_query(query_type='bolt', results=res,
query_time=query_time)
# Need to eventually add code to parse and display a network for the bolt format here
first_tab_html = ""
rows_and_columns = opencypher_get_rows_and_columns(res, True)
if rows_and_columns:
titles.append('Console')
table_id = f"table-{str(uuid.uuid4())[:8]}"
visible_results = results_per_page_check(args.results_per_page)
first_tab_html = opencypher_table_template.render(columns=rows_and_columns['columns'],
rows=rows_and_columns['rows'], guid=table_id,
amount=visible_results)
# Need to eventually add code to parse and display a network for the bolt format here

if not args.silent:
rows_and_columns = None
if args.mode != "explain":
rows_and_columns = opencypher_get_rows_and_columns(res, True if args.mode == 'bolt' else False)

display(tab)
first_tab_output = widgets.Output(layout=oc_layout)
# Assign an empty value so we can always display to table output.
table_html = ""

# Display Console Tab
# some issues with displaying a datatable when not wrapped in an hbox and displayed last
hbox = widgets.HBox([first_tab_output], layout=oc_layout)
children.append(hbox)
if rows_and_columns is not None:
titles.append('Console')
table_id = f"table-{str(uuid.uuid4())[:8]}"
visible_results = results_per_page_check(args.results_per_page)
table_html = opencypher_table_template.render(columns=rows_and_columns['columns'],
rows=rows_and_columns['rows'], guid=table_id,
amount=visible_results)

# Display Graph Tab (if exists)
if explain_html != "":
titles.append('Explain')
with first_tab_output:
display(HTML(explain_html))
else:
# Display Graph Tab (if exists)
if force_graph_output:
titles.append('Graph')
children.append(force_graph_output)

if args.mode != 'explain':
# Display JSON tab
json_output = widgets.Output(layout=oc_layout)
with json_output:
Expand All @@ -1840,17 +1831,22 @@ def handle_opencypher_query(self, line, cell, local_ns):
titles.append('Query Metadata')
children.append(metadata_output)

tab.children = children
if first_tab_html == "":
tab.children = children[1:] # the first tab is empty, remove it and proceed
else:
tab.children = children

for i in range(len(titles)):
tab.set_title(i, titles[i])

if table_html != "":
with first_tab_output:
display(HTML(table_html))
display(tab)

with metadata_output:
display(HTML(oc_metadata.to_html()))

if first_tab_html != "":
with first_tab_output:
display(HTML(first_tab_html))

store_to_ns(args.store_to, res, local_ns)

def handle_opencypher_status(self, line, local_ns):
Expand Down
11 changes: 10 additions & 1 deletion src/graph_notebook/network/opencypher/OCNetwork.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,8 +121,15 @@ def parse_node(self, node: dict, path_index: int = -2):

# generate placeholder tooltip from label; if not present, amalgamate node property values instead
if LABEL_KEY in node.keys():
title_plc = node[LABEL_KEY][0]
if len(node[LABEL_KEY]) > 0:
title_plc = node[LABEL_KEY][0]
create_title_placeholder = False
else:
create_title_placeholder = True
else:
create_title_placeholder = True

if create_title_placeholder:
title_plc = ""
for key in node:
title_plc += str(node[key])
Expand Down Expand Up @@ -164,6 +171,8 @@ def parse_node(self, node: dict, path_index: int = -2):

props = self.flatten(node)
label = self.get_node_property_value(node, props, title_plc, self.display_property)
if not label:
label = node[ID_KEY]
title, label = self.strip_and_truncate_label_and_title(label, self.label_max_length)
if self.tooltip_property and self.tooltip_property != self.display_property:
title, label_plc = self.strip_and_truncate_label_and_title(
Expand Down
83 changes: 82 additions & 1 deletion test/unit/network/opencypher/test_opencypher_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,33 @@ def test_default_groups_no_label(self):
node = gn.graph.nodes.get('22')
self.assertEqual(node['group'], 'DEFAULT_GROUP')

def test_default_groups_empty_label(self):
res = {
"results": [
{
"a": {
"~id": "22",
"~entityType": "node",
"~labels": [],
"~properties": {
"runways": 3,
"code": "SEA"
}
}
}
]
}

gn = OCNetwork(ignore_groups=True)
gn.add_results(res)
node = gn.graph.nodes.get('22')
self.assertEqual(node['group'], 'DEFAULT_GROUP')

gn = OCNetwork(group_by_property="code", ignore_groups=True)
gn.add_results(res)
node = gn.graph.nodes.get('22')
self.assertEqual(node['group'], 'DEFAULT_GROUP')

def test_group_with_groupby(self):
path = {
"results": [
Expand Down Expand Up @@ -1064,7 +1091,7 @@ def test_group_with_groupby_properties_json_multiple_labels(self):
self.assertEqual(node1['group'], 'ANC')
self.assertEqual(node2['group'], 'United States')

def test_set_add_non_graphable_results_list(self):
def test_add_non_graphable_results_list(self):
res = {
'results': [
{
Expand All @@ -1083,6 +1110,60 @@ def test_set_add_non_graphable_results_list(self):
self.assertEqual(nodes_list, [])
self.assertEqual(edges_list, [])

def test_add_empty_node_in_dict(self):
res = {
"results": [
{
"d": {
"~id": "6a6e5a7c-41ab-46e1-a8d1-397cc3e55294",
"~entityType": "node",
"~labels": [],
"~properties": {}
}
}
]
}

gn = OCNetwork()
try:
gn.add_results(res)
except IndexError:
self.fail()
nodes_list = list(gn.graph.nodes)
self.assertEqual(nodes_list, ["6a6e5a7c-41ab-46e1-a8d1-397cc3e55294"])

node = gn.graph.nodes.get("6a6e5a7c-41ab-46e1-a8d1-397cc3e55294")
self.assertEqual(node['label'], "6a6e5a7...")
self.assertEqual(node['title'], "6a6e5a7c-41ab-46e1-a8d1-397cc3e55294")

def test_add_empty_node_in_list(self):
res = {
"results": [
{
"p": [
{
'~id': "6a6e5a7c-41ab-46e1-a8d1-397cc3e55294",
'~entityType': "node",
"~labels": [],
"~properties": {}
}
]
}
]
}

gn = OCNetwork()
try:
gn.add_results(res)
except IndexError:
self.fail()
nodes_list = list(gn.graph.nodes)
self.assertEqual(nodes_list, ["6a6e5a7c-41ab-46e1-a8d1-397cc3e55294"])

node = gn.graph.nodes.get("6a6e5a7c-41ab-46e1-a8d1-397cc3e55294")
self.assertEqual(node['label'], "6a6e5a7...")
self.assertEqual(node['title'], "6a6e5a7c-41ab-46e1-a8d1-397cc3e55294")

def test_do_not_set_vertex_label_or_vertex_tooltip(self):
res = {
"results": [
Expand Down

0 comments on commit 45c3fc6

Please sign in to comment.