From 45f5fd1920bf5b93459f16df097224c1c2d0ed50 Mon Sep 17 00:00:00 2001 From: t92549 <80890692+t92549@users.noreply.github.com> Date: Fri, 15 Jul 2022 11:40:05 +0100 Subject: [PATCH] Graph layout is now saved when navigate away from page (#812) (#1011) * Graph layout is now saved when navigate away from page * Fixed issue with old graph results still showing after clear * Updating removed elements to use concat * Updating to use forEach Updating removePreviouslyRemovedElements to use forEach * Updated syntax error * Altered foreach syntax due to jasmine errors * gh-496: Fix bug with concat Co-authored-by: t11947 <53758970+t11947@users.noreply.github.com> Co-authored-by: t92549 <80890692+t92549@users.noreply.github.com> Co-authored-by: p013570 Co-authored-by: t11947 <53758970+t11947@users.noreply.github.com> --- .../main/webapp/app/graph/graph-component.js | 44 ++++++++++++++++-- ui/src/main/webapp/app/graph/graph-service.js | 40 ++++++++++++++++ .../webapp/app/toolbar/toolbar-component.js | 4 +- .../webapp/app/graph/graph-component-spec.js | 46 ++++++++++++++++++- .../webapp/app/graph/graph-service-spec.js | 42 +++++++++++++++++ 5 files changed, 169 insertions(+), 7 deletions(-) diff --git a/ui/src/main/webapp/app/graph/graph-component.js b/ui/src/main/webapp/app/graph/graph-component.js index 99912ecd6..261e3cf16 100644 --- a/ui/src/main/webapp/app/graph/graph-component.js +++ b/ui/src/main/webapp/app/graph/graph-component.js @@ -140,6 +140,7 @@ function GraphController($q, graph, config, error, loading, query, operationOpti events.unsubscribe('resultsCleared', vm.reset); if (cytoscapeGraph) { + saveGraph(); cytoscapeGraph.destroy(); } } @@ -151,15 +152,35 @@ function GraphController($q, graph, config, error, loading, query, operationOpti createCytoscapeGraph().then(function(cy) { cytoscapeGraph = cy; generateStylesheets(); - vm.reset() + + if(graph.hasGraphJson()) { + restoreGraph(); + cytoscapeGraph.elements().lock(); + vm.update(results.get()); + cytoscapeGraph.elements().unlock(); + } else { + vm.reset(); + } vm.graphLoading = false; var searchTerm = graph.getSearchTerm(); - if (searchTerm !== null && searchTerm !== undefined && searchTerm !== "") { vm.filter(searchTerm) } }); } + + var saveGraph = function() { + if(cytoscapeGraph) { + graph.setGraphJson(cytoscapeGraph.json()); + } + } + + var restoreGraph = function() { + if(graph.hasGraphJson() && cytoscapeGraph && (results.get().edges || results.get().entities)) { + cytoscapeGraph.json(graph.getGraphJson()); + cytoscapeGraph.nodes().removeClass("filtered"); // seems to be a bug in cytoscape json + } + } /** * Loads cytoscape graph onto an element containing the "graphCy" id. It also registers the @@ -416,6 +437,7 @@ function GraphController($q, graph, config, error, loading, query, operationOpti cytoscapeGraph.getElementById(id).data(elementsToMergeData[id]); } + removePreviouslyRemovedElements(); vm.redraw(); }); @@ -598,6 +620,8 @@ function GraphController($q, graph, config, error, loading, query, operationOpti */ vm.reset = function() { vm.clear(); + graph.setRemovedElements([]); + saveGraph(); vm.update(results.get()); } @@ -610,7 +634,7 @@ function GraphController($q, graph, config, error, loading, query, operationOpti searchTerm = searchTerm.toLowerCase(); cytoscapeGraph.batch(function() { var nodes = cytoscapeGraph.nodes(); - for(var i in nodes) { + for(var i = 0; i < nodes.length; i++) { if(nodes[i].data && nodes[i].data('id')) { if(nodes[i].data('id').toLowerCase().indexOf(searchTerm) === -1) { nodes[i].addClass("filtered"); @@ -630,9 +654,21 @@ function GraphController($q, graph, config, error, loading, query, operationOpti * Removes every selected element in the graph. */ vm.removeSelected = function() { - cytoscapeGraph.filter(":selected").remove(); + var removedElements = cytoscapeGraph.filter(":selected").remove().toArray(); + graph.setRemovedElements(graph.getRemovedElements().concat(removedElements)); + cytoscapeGraph.elements().unselect(); vm.selectedElements.entities = []; vm.selectedElements.edges = []; } + + var removePreviouslyRemovedElements = function() { + var elements = cytoscapeGraph.elements(); + graph.getRemovedElements().forEach(function(removedElement) { + var element = elements.getElementById(removedElement.id()); + if(element && element.id()) { + element.remove(); + } + }); + } } diff --git a/ui/src/main/webapp/app/graph/graph-service.js b/ui/src/main/webapp/app/graph/graph-service.js index 9109a29eb..3de92f0ae 100644 --- a/ui/src/main/webapp/app/graph/graph-service.js +++ b/ui/src/main/webapp/app/graph/graph-service.js @@ -24,6 +24,9 @@ angular.module('app').factory('graph', function() { var graphConfiguration = null; + var removedElements = []; + var graphJson = {}; + var selectedElements = { entities: [], edges: [] @@ -76,6 +79,43 @@ angular.module('app').factory('graph', function() { searchTerm = angular.copy(search); } + /** + * Gets the removed elements. + */ + service.getRemovedElements = function() { + return removedElements; + } + + /** + * Sets the removed elements. + * @param removedElements; + */ + service.setRemovedElements = function(newRemovedElements) { + removedElements = newRemovedElements + } + + /** + * Checks if the graph json has been set. + */ + service.hasGraphJson = function() { + return graphJson && Object.keys(graphJson).length > 0 && Object.keys(graphJson.elements).length > 0; + } + + /** + * Gets the graph json. + */ + service.getGraphJson = function() { + return graphJson; + } + + /** + * Sets the graph json. + * @param graphJson; + */ + service.setGraphJson = function(newGraphJson) { + graphJson = newGraphJson; + } + /** * Resets the value of the selected elements */ diff --git a/ui/src/main/webapp/app/toolbar/toolbar-component.js b/ui/src/main/webapp/app/toolbar/toolbar-component.js index f1fdfc4b2..a0721a9b0 100644 --- a/ui/src/main/webapp/app/toolbar/toolbar-component.js +++ b/ui/src/main/webapp/app/toolbar/toolbar-component.js @@ -26,7 +26,7 @@ function toolbar() { }; } -function ToolbarController($rootScope, $mdDialog, operationService, results, query, config, loading, events, properties, error, $mdToast, $cookies) { +function ToolbarController($rootScope, $mdDialog, operationService, results, query, config, loading, events, properties, error, $mdToast, $cookies, graph) { var vm = this; vm.addMultipleSeeds = false; vm.appTitle; @@ -165,6 +165,7 @@ function ToolbarController($rootScope, $mdDialog, operationService, results, que vm.executeAll = function() { var ops = query.getOperations(); + graph.setGraphJson(null); if (ops.length > 0) { results.clear(false); loading.load(); @@ -175,6 +176,7 @@ function ToolbarController($rootScope, $mdDialog, operationService, results, que } vm.clearResults = function() { + graph.setGraphJson(null); results.clear(); } diff --git a/ui/src/test/webapp/app/graph/graph-component-spec.js b/ui/src/test/webapp/app/graph/graph-component-spec.js index 4e5bd7510..3b5699451 100644 --- a/ui/src/test/webapp/app/graph/graph-component-spec.js +++ b/ui/src/test/webapp/app/graph/graph-component-spec.js @@ -308,6 +308,24 @@ describe("The Graph Component", function() { expect(ctrl.update).toHaveBeenCalled(); }) + it('should load the graph from the saved graph json when it exists', function() { + var graphJson = {elements: [1,2]}; + spyOn(ctrl, 'update').and.stub(); + $httpBackend.whenGET('config/config.json').respond(200, {}); + graph.setGraphJson(graphJson); + spyOn(injectableCytoscape, 'json') + + ctrl.$onInit(); + + $httpBackend.flush(); + jasmine.clock().tick(101); + + scope.$digest(); + + expect(injectableCytoscape.json).toHaveBeenCalledWith(graphJson); + expect(ctrl.update).toHaveBeenCalled(); + }) + it('should run the filter once loaded if the service holds a filter', function() { spyOn(ctrl, 'filter').and.stub(); spyOn(ctrl, 'update').and.stub(); @@ -447,7 +465,16 @@ describe("The Graph Component", function() { expect(events.unsubscribe).toHaveBeenCalledWith("incomingResults", jasmine.any(Function)); }); - + + it('should save the graph json', function() { + var graphJson = {elements: [1,2]}; + spyOn(injectableCytoscape, 'json').and.returnValue(graphJson); + + ctrl.$onDestroy(); + expect(injectableCytoscape.json).toHaveBeenCalled(); + expect(graph.getGraphJson()).toEqual(graphJson); + }); + it('should destroy the cytoscape graph cleanly', function() { spyOn(injectableCytoscape, 'destroy'); @@ -649,7 +676,15 @@ describe("The Graph Component", function() { expect(injectableCytoscape.nodes().size()).toEqual(1); }); - + + it('should save the removed elements', function() { + graph.setRemovedElements([]); + ctrl.removeSelected(); + + expect(injectableCytoscape.nodes().size()).toEqual(1); + expect(graph.getRemovedElements().length).toEqual(2); + }); + it('should unselect all the elements', function() { expect(injectableCytoscape.elements(':selected').size()).toEqual(1); @@ -680,6 +715,13 @@ describe("The Graph Component", function() { expect(injectableCytoscape.elements().size()).toEqual(3); }); + it('should remove previously removed elements from the results', function() { + graph.setRemovedElements([{id: function(){return "\"foo\""}}]); + injectableCytoscape.elements().restore(); + ctrl.update(elements); + expect(injectableCytoscape.elements().size()).toEqual(1); + }); + it('should not error when vertex type is unknown', function() { entityVertexType = undefined; diff --git a/ui/src/test/webapp/app/graph/graph-service-spec.js b/ui/src/test/webapp/app/graph/graph-service-spec.js index e0e800385..8ae24ed9d 100644 --- a/ui/src/test/webapp/app/graph/graph-service-spec.js +++ b/ui/src/test/webapp/app/graph/graph-service-spec.js @@ -83,6 +83,48 @@ describe('The Graph service', function() { }); }); + describe('graph.getRemovedElements()', function() { + it('should set and get the removed elements', function() { + var removedElements = [1,3,5,7]; + service.setRemovedElements(removedElements); + + var result = service.getRemovedElements(); + + expect(result).toEqual(removedElements); + }) + }); + + describe('graph.getGraphJson()', function() { + it('should set and get the graph json', function() { + var graphJson = {elements: [1,2]}; + service.setGraphJson(graphJson); + + var result = service.getGraphJson(); + + expect(result).toEqual(graphJson); + }) + }); + + describe('graph.hasGraphJson()', function() { + it('should return true when the graph json is set', function() { + var graphJson = {elements: [1,2]}; + service.setGraphJson(graphJson); + + var result = service.hasGraphJson(); + + expect(result).toEqual(true); + }) + + it('should return false when the graph json is not set', function() { + var graphJson = {elements: []}; + service.setGraphJson(graphJson); + + var result = service.hasGraphJson(); + + expect(result).toEqual(false); + }) + }); + describe('graph.deselectAll()', function() { beforeEach(function() {