From 0a0c3a45dbfedb1b6625372b94e28658ec2b1c51 Mon Sep 17 00:00:00 2001 From: Michael Chin Date: Thu, 29 Jul 2021 20:34:15 -0700 Subject: [PATCH] Fix incorrect visualizations of Gremlin results returned by valueMap (#165) * Fix Gremlin visualization bug * Update Changelog * Another Changelog update Co-authored-by: Michael Chin --- ChangeLog.md | 6 ++ .../network/gremlin/GremlinNetwork.py | 83 +++++++++++-------- .../network/gremlin/test_gremlin_network.py | 57 +++++++++++++ 3 files changed, 113 insertions(+), 33 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index d791f938..62613aac 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -4,6 +4,12 @@ Starting with v1.31.6, this file will contain a record of major features and upd ## Upcoming +- Add new Knowledge Graph use case notebook for openCypher usage ([Link to PR](https://github.com/aws/graph-notebook/pull/161)) +- Fixed incorrect visualizations of some Gremlin results returned by valueMap ([Link to PR](https://github.com/aws/graph-notebook/pull/165)) +- Fixed error with Gremlin visualizer incorrectly identifying some query results as elementMaps ([Link to PR](https://github.com/aws/graph-notebook/pull/158)) +- Pin RDFLib version in README ([Link to PR](https://github.com/aws/graph-notebook/pull/162)) +- Fixed inconsistent node tooltips in openCypher visualizations ([Link to PR](https://github.com/aws/graph-notebook/pull/163)) + ## Release 3.0.1 (July 28, 2021) **openCypher Support**: diff --git a/src/graph_notebook/network/gremlin/GremlinNetwork.py b/src/graph_notebook/network/gremlin/GremlinNetwork.py index 7920d6aa..0ce37fc4 100644 --- a/src/graph_notebook/network/gremlin/GremlinNetwork.py +++ b/src/graph_notebook/network/gremlin/GremlinNetwork.py @@ -240,41 +240,23 @@ def add_results(self, results): for i in range(len(path)): if isinstance(path[i], dict): - self.insert_elementmap(path[i]) - else: - if i == 0: - self.add_vertex(path[i]) - continue - - if type(path[i]) is Edge: - edge = path[i] - path_left = get_id(path[i - 1]) - path_right = get_id(path[i + 1]) - - # If the edge is an object type but its vertices aren't, then the ID contained - # in the edge won't be the same as the ids used to store those two vertices. - # For example, g.V().inE().outV().path().by(valueMap()).by().by(valueMap(true) - # will yield a V, E, V pattern where the first vertex is a dict without T.id, the edge - # will be an Edge object, and the second vertex will be a dict with T.id. - if edge.outV.id == path_left or edge.inV.id == path_right: - from_id = path_left - to_id = path_right - self.add_path_edge(path[i], from_id, to_id) - elif edge.inV.id == path_left or edge.outV.id == path_right: - from_id = path_right - to_id = path_left - self.add_path_edge(path[i], from_id, to_id) - else: - from_id = path_left - to_id = path_right - self.add_blank_edge(from_id, to_id, edge.id, label=edge.label) - continue + is_elementmap = False + for prop, value in path[i].items(): + if prop not in [T.id, T.label] and isinstance(value, str): + is_elementmap = True + break + elif isinstance(value, dict): + is_elementmap = True + break + elif isinstance(value, list): + break + if is_elementmap: + self.insert_elementmap(path[i]) else: - from_id = get_id(path[i - 1]) + self.insert_path_element(path, i) + else: + self.insert_path_element(path, i) - self.add_vertex(path[i]) - if type(path[i - 1]) is not Edge: - self.add_blank_edge(from_id, get_id(path[i])) elif isinstance(path, dict) and T.id in path.keys() and T.label in path.keys(): self.insert_elementmap(path) else: @@ -452,6 +434,41 @@ def add_blank_edge(self, from_id, to_id, edge_id=None, undirected=True, label='' edge_data = UNDIRECTED_EDGE if undirected else {} self.add_edge(from_id, to_id, edge_id, label, edge_data) + def insert_path_element(self, path, i): + if i == 0: + self.add_vertex(path[i]) + return + + if type(path[i]) is Edge: + edge = path[i] + path_left = get_id(path[i - 1]) + path_right = get_id(path[i + 1]) + + # If the edge is an object type but its vertices aren't, then the ID contained + # in the edge won't be the same as the ids used to store those two vertices. + # For example, g.V().inE().outV().path().by(valueMap()).by().by(valueMap(true) + # will yield a V, E, V pattern where the first vertex is a dict without T.id, the edge + # will be an Edge object, and the second vertex will be a dict with T.id. + if edge.outV.id == path_left or edge.inV.id == path_right: + from_id = path_left + to_id = path_right + self.add_path_edge(path[i], from_id, to_id) + elif edge.inV.id == path_left or edge.outV.id == path_right: + from_id = path_right + to_id = path_left + self.add_path_edge(path[i], from_id, to_id) + else: + from_id = path_left + to_id = path_right + self.add_blank_edge(from_id, to_id, edge.id, label=edge.label) + return + else: + from_id = get_id(path[i - 1]) + + self.add_vertex(path[i]) + if type(path[i - 1]) is not Edge: + self.add_blank_edge(from_id, get_id(path[i])) + def insert_elementmap(self, e_map): """ Add a vertex or edge that has been returned by an elementMap query step. diff --git a/test/unit/network/gremlin/test_gremlin_network.py b/test/unit/network/gremlin/test_gremlin_network.py index f5a1aeef..a27023b9 100644 --- a/test/unit/network/gremlin/test_gremlin_network.py +++ b/test/unit/network/gremlin/test_gremlin_network.py @@ -1523,6 +1523,63 @@ def test_add_results_as_path_containing_elementmaps(self): self.assertEqual(inv_data['properties'], in_vertex) self.assertEqual(edge_data, edge_expected) + def test_add_results_as_path_containing_valuemaps(self): + + out_vertex = { + 'country': ['US'], + 'code': ['SAF'], + 'longest': [8366], + 'city': ['Santa Fe'], + 'lon': [-106.088996887], + 'type': ['airport'], + T.id: '44', 'elev': [6348], + T.label: 'airport', + 'icao': ['KSAF'], + 'runways': [3], + 'region': ['US-NM'], + 'lat': [35.617099762], + 'desc': ['Santa Fe'] + } + + in_vertex = { + 'country': ['US'], + 'code': ['DFW'], + 'longest': [13401], + 'city': ['Dallas'], + 'lon': [-97.0380020141602], + 'type': ['airport'], + T.id: '8', + 'elev': [607], + T.label: 'airport', + 'icao': ['KDFW'], + 'runways': [7], + 'region': ['US-TX'], + 'lat': [32.896800994873], + 'desc': ['Dallas/Fort Worth International Airport'] + } + + edge_value_expected = { + 'arrows': { + 'to': { + 'enabled': False + } + }, + 'label': '' + } + + path = Path([], [out_vertex, in_vertex]) + gn = GremlinNetwork() + gn.add_results([path]) + edge_data = gn.graph.get_edge_data('44', '8') + for prop, value in edge_data.items(): + edge_data_value = value + break + outv_data = gn.graph.nodes.get('44') + inv_data = gn.graph.nodes.get('8') + self.assertEqual(outv_data['properties'], out_vertex) + self.assertEqual(inv_data['properties'], in_vertex) + self.assertEqual(edge_data_value, edge_value_expected) + if __name__ == '__main__': unittest.main()