diff --git a/CHANGELOG b/CHANGELOG index 5caf9c529b..d1de3ddf99 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,8 @@ v1.4 v1.3.1 (2013-XX-XX) ------------------- +* issue #540: suppress return of temporary internal variables in AQL + * issue #530: ReferenceError: ArangoError is not a constructor * issue #535: Problem with AQL user functions javascript API diff --git a/Documentation/Makefile.files b/Documentation/Makefile.files index dc1feb042a..6e395033f3 100644 --- a/Documentation/Makefile.files +++ b/Documentation/Makefile.files @@ -310,7 +310,7 @@ CLEANUP += \ swagger: python @srcdir@/Documentation/Scripts/generateSwagger.py \ - < @srcdir@/arangod/RestHandler/RestEdgeHandler.cpp > @srcdir@/html/admin/api-docs/edges + < @srcdir@/arangod/RestHandler/RestEdgeHandler.cpp > @srcdir@/html/admin/api-docs/edge python @srcdir@/Documentation/Scripts/generateSwagger.py \ < @srcdir@/arangod/RestHandler/RestDocumentHandler.cpp > @srcdir@/html/admin/api-docs/document diff --git a/Documentation/Manual/NewFeatures11.md b/Documentation/Manual/NewFeatures11.md index 27bf661f31..89643a3b31 100644 --- a/Documentation/Manual/NewFeatures11.md +++ b/Documentation/Manual/NewFeatures11.md @@ -209,8 +209,7 @@ Server Statistics {#NewFeatures11ServerStatistics} -------------------------------------------------- ArangoDB 1.1 allows querying the server status via the administration -front-end (see @ref UserManualWebInterfaceStatistics) or via REST API -methods. +front-end or via REST API methods. The following methods are available: - `GET /_admin/connection-statistics`: provides connection statistics diff --git a/Documentation/UserManual/WebInterface.md b/Documentation/UserManual/WebInterface.md index 506836f142..1b36bd1a86 100644 --- a/Documentation/UserManual/WebInterface.md +++ b/Documentation/UserManual/WebInterface.md @@ -7,7 +7,7 @@ ArangoDB's Web-Interface {#UserManualWebInterface} Accessing the Web-Interface {#UserManualWebInterfaceAccess} =========================================================== -The web interfaced can be access as +The web interface can be accessed via the URL http://localhost:8529 @@ -20,64 +20,55 @@ application instead. In this case use Collections Tab {#UserManualWebInterfaceCollections} ---------------------------------------------------- -The collection tabs shows an overview about the loaded and unloaded -collections of the database. +The *Collections* tab shows an overview of the loaded and unloaded +collections present in ArangoDB. System collections (i.e. collections +whose names start with an underscore) are not shown by default. -@htmlonly ArangoDB Front-End@endhtmlonly -@latexonly\includegraphics[width=12cm]{images/fe-collections.png}@endlatexonly +The list of collections can be restricted using the search bar, or by +using the filtering at the top. The filter can also be used to show or +hide system collections. -You can load, unloaded, delete, or inspect the collections. Please -note that you should not delete or change system collections, i. e., -collections starting with an underscore. +Clicking on a collection will show the documents contained in it. +Clicking the small icon on a collection's badge will bring up a dialog +that allows loading/unloading, renaming and deleting the collection. -If you click on the magnifying glass, you will get a list of all documents -in the collection. +Please note that you should not change or delete system collections. -@htmlonly ArangoDB Front-End@endhtmlonly -@latexonly\includegraphics[width=12cm]{images/fe-documents.png}@endlatexonly +In the list of documents of a collection, you can click on the *Add document* +line to add a new document to the collection. The document will be created +instantly, with a system-defined key. The key and all other attributes of the +document can be adjusted in the following view. -Using the pencil you can edit the document. -Query Tab {#UserManualWebInterfaceQuery} ----------------------------------------- +AQL Editor Tab {#UserManualWebInterfaceQuery} +--------------------------------------------- -The query tabs allows you to execute AQL queries. +The *AQL Editor* tab allow to execute AQL queries. -@htmlonly ArangoDB Front-End@endhtmlonly -@latexonly\includegraphics[width=12cm]{images/fe-query.png}@endlatexonly +Type in a query in the bottom box and execute it by pressing the *Submit* button. +The query result will be shown in the box at the top. -Type in a query and execute it. -Shell Tab {#UserManualWebInterfaceShell} ----------------------------------------- +JS Shell Tab {#UserManualWebInterfaceShell} +------------------------------------------- -The shell tabs give you access to a JavaScript shell connection to the +The *JS Shell* tab provides access to a JavaScript shell connection to the database server. -@htmlonly ArangoDB Front-End@endhtmlonly -@latexonly\includegraphics[width=12cm]{images/fe-shell.png}@endlatexonly +Any valid JavaScript code can be executed inside the shell. The code will be +executed inside your browser. To contact the ArangoDB server, you can use the +`db` object, for example as follows: + + JSH> db._create("mycollection"); + JSH> db.mycollection.save({ _key: "test", value: "something" }); -Use the OK button or return to execute a command. Logs Tab {#UserManualWebInterfaceLogs} -------------------------------------- -You can browse the log files. - -@htmlonly ArangoDB Front-End@endhtmlonly -@latexonly\includegraphics[width=12cm]{images/fe-logs.png}@endlatexonly +You can use the *Logs* tab to browse the most recent log entries provided by the +ArangoDB database server. Note that the server only keeps a limited number of log entries. For real log analyses write the logs to disk using syslog or a similar -mechanism. - -Statistics Tab {#UserManualWebInterfaceStatistics} --------------------------------------------------- - -Use the statistics tab to display information about the server. - -@htmlonly ArangoDB Front-End@endhtmlonly -@latexonly\includegraphics[width=12cm]{images/fe-statistics.png}@endlatexonly - -Initially no statistics will be display. You must use the add button -to configure what type of information should be displayed. +mechanism. ArangoDB provides several startup options for this. diff --git a/Documentation/UserManual/WebInterfaceTOC.md b/Documentation/UserManual/WebInterfaceTOC.md index 4a29ec7410..a17c13ad43 100644 --- a/Documentation/UserManual/WebInterfaceTOC.md +++ b/Documentation/UserManual/WebInterfaceTOC.md @@ -6,4 +6,3 @@ TOC {#UserManualWebInterfaceTOC} - @ref UserManualWebInterfaceQuery - @ref UserManualWebInterfaceShell - @ref UserManualWebInterfaceLogs - - @ref UserManualWebInterfaceStatistics diff --git a/arangod/Ahuacatl/ahuacatl-codegen.c b/arangod/Ahuacatl/ahuacatl-codegen.c index 9ae2d7a5a6..a86c242f74 100644 --- a/arangod/Ahuacatl/ahuacatl-codegen.c +++ b/arangod/Ahuacatl/ahuacatl-codegen.c @@ -668,7 +668,7 @@ static void InitArray (TRI_aql_codegen_js_t* const generator, static void EnterSymbol (TRI_aql_codegen_js_t* const generator, const char* const name, const TRI_aql_codegen_register_t registerIndex) { - TRI_aql_codegen_scope_t* scope = CurrentScope(generator); + TRI_aql_codegen_scope_t* scope = CurrentScope(generator); TRI_aql_codegen_variable_t* variable = CreateVariable(name, registerIndex); if (variable == NULL) { @@ -890,7 +890,8 @@ static void CloseLoops (TRI_aql_codegen_js_t* const generator) { //////////////////////////////////////////////////////////////////////////////// static TRI_vector_string_t StoreSymbols (TRI_aql_codegen_js_t* const generator, - const TRI_aql_codegen_register_t rowRegister) { + const TRI_aql_codegen_register_t rowRegister, + bool suppressTemporaries) { TRI_vector_string_t variableNames; size_t i = generator->_scopes._length; @@ -907,7 +908,12 @@ static TRI_vector_string_t StoreSymbols (TRI_aql_codegen_js_t* const generator, TRI_aql_codegen_variable_t* variable = (TRI_aql_codegen_variable_t*) peek->_variables._table[j]; char* copy; - if (! variable) { + if (variable == NULL) { + continue; + } + + if (suppressTemporaries && *variable->_name == '_') { + // don't emit temporary variables continue; } @@ -957,6 +963,7 @@ static void RestoreSymbols (TRI_aql_codegen_js_t* const generator, // iterate over all variables passed n = variableNames->_length; + for (i = 0; i < n; ++i) { // create a new register for each variable TRI_aql_codegen_register_t registerIndex = IncRegister(generator); @@ -2001,7 +2008,7 @@ static void ProcessSort (TRI_aql_codegen_js_t* const generator, InitArray(generator, rowRegister); // save symbols - variableNames = StoreSymbols(generator, rowRegister); + variableNames = StoreSymbols(generator, rowRegister, false); // result.push(row) ScopeOutputRegister(generator, scope->_resultRegister); @@ -2050,13 +2057,17 @@ static void ProcessCollect (TRI_aql_codegen_js_t* const generator, TRI_aql_node_t* list; size_t i, n; +#if AQL_VERBOSE + ScopeOutput(generator, "\n/* collect start */\n"); +#endif + // var row = { }; InitArray(generator, rowRegister); // we are not interested in the variable names here, but // StoreSymbols also generates code to save the current state in // a rowRegister, and we need that - variableNames = StoreSymbols(generator, rowRegister); + variableNames = StoreSymbols(generator, rowRegister, true); TRI_DestroyVectorString(&variableNames); // result.push(row) @@ -2133,6 +2144,10 @@ static void ProcessCollect (TRI_aql_codegen_js_t* const generator, EnterSymbol(generator, TRI_AQL_NODE_STRING(nameNode), intoRegister); } + +#if AQL_VERBOSE + ScopeOutput(generator, "\n/* collect end */\n"); +#endif } //////////////////////////////////////////////////////////////////////////////// @@ -2152,7 +2167,7 @@ static void ProcessLimit (TRI_aql_codegen_js_t* const generator, InitArray(generator, rowRegister); // save symbols - variableNames = StoreSymbols(generator, rowRegister); + variableNames = StoreSymbols(generator, rowRegister, false); // result.push(row) ScopeOutputRegister(generator, scope->_resultRegister); diff --git a/arangod/RestHandler/RestDocumentHandler.cpp b/arangod/RestHandler/RestDocumentHandler.cpp index 409b0dad78..d4b3eac785 100644 --- a/arangod/RestHandler/RestDocumentHandler.cpp +++ b/arangod/RestHandler/RestDocumentHandler.cpp @@ -510,7 +510,7 @@ bool RestDocumentHandler::readDocument () { /// /// Use a document handle: /// -/// @EXAMPLE_ARANGOSH_RUN{RestReadDocument} +/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerReadDocument} /// var cn = "products"; /// db._drop(cn); /// db._create(cn); @@ -527,7 +527,7 @@ bool RestDocumentHandler::readDocument () { /// /// Use a document handle and an etag: /// -/// @EXAMPLE_ARANGOSH_RUN{RestReadDocumentIfNoneMatch} +/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerReadDocumentIfNoneMatch} /// var cn = "products"; /// db._drop(cn); /// db._create(cn); @@ -545,7 +545,7 @@ bool RestDocumentHandler::readDocument () { /// /// Unknown document handle: /// -/// @EXAMPLE_ARANGOSH_RUN{RestReadDocumentUnknownHandle} +/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerReadDocumentUnknownHandle} /// var url = "/_api/document/products/unknownhandle"; /// /// var response = logCurlRequest('GET', url); @@ -666,7 +666,7 @@ bool RestDocumentHandler::readSingleDocument (bool generateBody) { /// /// Returns a collection. /// -/// @EXAMPLE_ARANGOSH_RUN{RestReadDocumentAll} +/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerReadDocumentAll} /// var cn = "products"; /// db._drop(cn); /// db._create(cn); @@ -685,7 +685,7 @@ bool RestDocumentHandler::readSingleDocument (bool generateBody) { /// /// Collection does not exist. /// -/// @EXAMPLE_ARANGOSH_RUN{RestReadDocumentAllCollectionDoesNotExist} +/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerReadDocumentAllCollectionDoesNotExist} /// var cn = "doesnotexist"; /// db._drop(cn); /// var url = "/_api/document/?collection=" + cn; @@ -811,7 +811,7 @@ bool RestDocumentHandler::readAllDocuments () { /// /// @EXAMPLES /// -/// @EXAMPLE_ARANGOSH_RUN{RestReadDocumentHead} +/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerReadDocumentHead} /// var cn = "products"; /// db._drop(cn); /// db._create(cn); @@ -962,7 +962,7 @@ bool RestDocumentHandler::checkDocument () { /// /// Using document handle: /// -/// @EXAMPLE_ARANGOSH_RUN{RestUpdateDocument} +/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerUpdateDocument} /// var cn = "products"; /// db._drop(cn); /// db._create(cn); @@ -979,7 +979,7 @@ bool RestDocumentHandler::checkDocument () { /// /// Unknown document handle: /// -/// @EXAMPLE_ARANGOSH_RUN{RestUpdateDocumentUnknownHandle} +/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerUpdateDocumentUnknownHandle} /// var cn = "products"; /// db._drop(cn); /// db._create(cn); @@ -997,7 +997,7 @@ bool RestDocumentHandler::checkDocument () { /// /// Produce a revision conflict: /// -/// @EXAMPLE_ARANGOSH_RUN{RestUpdateDocumentIfMatchOther} +/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerUpdateDocumentIfMatchOther} /// var cn = "products"; /// db._drop(cn); /// db._create(cn); @@ -1016,7 +1016,7 @@ bool RestDocumentHandler::checkDocument () { /// /// Last write wins: /// -/// @EXAMPLE_ARANGOSH_RUN{RestUpdateDocumentIfMatchOtherLastWriteWins} +/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerUpdateDocumentIfMatchOtherLastWriteWins} /// var cn = "products"; /// db._drop(cn); /// db._create(cn); @@ -1034,7 +1034,7 @@ bool RestDocumentHandler::checkDocument () { /// /// Alternative to header field: /// -/// @EXAMPLE_ARANGOSH_RUN{RestUpdateDocumentRevOther} +/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerUpdateDocumentRevOther} /// var cn = "products"; /// db._drop(cn); /// db._create(cn); @@ -1151,7 +1151,7 @@ bool RestDocumentHandler::replaceDocument () { /// /// patches an existing document with new content. /// -/// @EXAMPLE_ARANGOSH_RUN{RestPatchDocument} +/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerPatchDocument} /// var cn = "products"; /// db._drop(cn); /// db._create(cn); @@ -1392,7 +1392,7 @@ bool RestDocumentHandler::modifyDocument (bool isPatch) { /// /// Using document handle: /// -/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerDELETEDocument} +/// @EXAMPLE_ARANGOSH_RUN{RestDocumentHandlerDeleteDocument} /// var cn = "products"; /// db._drop(cn); /// db._create(cn, { waitForSync: true }); diff --git a/arangod/RestHandler/RestEdgeHandler.cpp b/arangod/RestHandler/RestEdgeHandler.cpp index e6cab8be4a..8fd712fc4b 100644 --- a/arangod/RestHandler/RestEdgeHandler.cpp +++ b/arangod/RestHandler/RestEdgeHandler.cpp @@ -75,30 +75,63 @@ RestEdgeHandler::RestEdgeHandler (HttpRequest* request, TRI_vocbase_t* vocbase) /// /// @RESTHEADER{POST /_api/edge,creates an edge} /// -/// @REST{POST /_api/edge?collection=`collection-name`&from=`from-handle`&to=`to-handle`} +/// @RESTQUERYPARAMETERS /// -/// Creates a new edge in the collection identified by `collection-name`. +/// @RESTQUERYPARAM{collection,string,required} +/// Creates a new edge in the collection identified by `collection` name. +/// +/// @RESTQUERYPARAM{from,string,required} +/// The document handle of the start point must be passed in `from` handle. +/// +/// @RESTQUERYPARAM{to,string,required} +/// The document handle of the end point must be passed in `to` handle. +/// +/// @RESTBODYPARAM{edge-document,json,required} /// A JSON representation of the edge document must be passed as the body of /// the POST request. This JSON object may contain the edge's document key in /// the `_key` attribute if needed. -/// The document handle of the start point must be passed in `from-handle`. -/// The document handle of the end point must be passed in `to-handle`. /// -/// `from-handle` and `to-handle` are immutable once the edge has been +/// @RESTDESCRIPTION +/// `from` handle and `to` handle are immutable once the edge has been /// created. /// /// In all other respects the method works like `POST /document`, see /// @ref RestDocument for details. /// +/// @RESTRETURNCODES +/// +/// @RESTRETURNCODE{202} +/// is returned if the edge was created successfully. +/// +/// @RESTRETURNCODE{404} +/// is returned if the edge collection was not found. +/// /// @EXAMPLES /// -/// Create an edge: +/// Create an edge and reads it back: /// -/// @verbinclude rest-edge-create-edge +/// @EXAMPLE_ARANGOSH_RUN{RestEdgeCreateEdge} +/// db._drop("edges"); +/// db._drop("vertices"); +/// var Graph = require("org/arangodb/graph").Graph; +/// var g = new Graph("graph", "vertices", "edges"); +/// g.addVertex(1); +/// g.addVertex(2); +/// var url = "/_api/edge/?collection=edges&from=vertices/1&to=vertices/2"; +/// +/// var response = logCurlRequest("POST", url, { "name": "Emil" }); +/// +/// assert(response.code === 202); /// -/// Read an edge: +/// logJsonResponse(response); +/// var body = response.body.replace(/\\/g, ''); +/// var edge_id = JSON.parse(body)._id; +/// var response2 = logCurlRequest("GET", "/_api/edge/" + edge_id); +/// +/// assert(response2.code === 200); /// -/// @verbinclude rest-edge-read-edge +/// logJsonResponse(response2); +/// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// bool RestEdgeHandler::createDocument () { diff --git a/html/admin/api-docs.json b/html/admin/api-docs.json index cbfd2c3cd1..05abc74afd 100644 --- a/html/admin/api-docs.json +++ b/html/admin/api-docs.json @@ -1,5 +1,5 @@ { - "apiVersion":"1.3", + "apiVersion":"1.4", "swaggerVersion":"1.1", "apis":[ { "path": "api-docs/document", @@ -9,6 +9,10 @@ "path": "api-docs/edges", "description": "Interface für Edges" }, + { + "path": "api-docs/edge", + "description": "Interface für Edge" + }, { "path": "api-docs/collection", "description": "Interface for Collections" diff --git a/html/admin/css/footerView.css b/html/admin/css/footerView.css index 131a119937..68dd6d7bff 100644 --- a/html/admin/css/footerView.css +++ b/html/admin/css/footerView.css @@ -1,10 +1,11 @@ .footer { + position: fixed; background: url("../img/footer_hd_bg.png") repeat-x scroll left 10px #4A4A4A; font-size: 14px; text-align: center; left: 0; right: 0; - margin-top:500px; + bottom: 0px; } .footerExtra { diff --git a/html/admin/css/graphView.css b/html/admin/css/graphView.css index 0d2e22b324..be29b16aa0 100644 --- a/html/admin/css/graphView.css +++ b/html/admin/css/graphView.css @@ -22,11 +22,27 @@ background-color: rgb(56, 52, 52); } -.searchByAttribute { +.searchByAttribute, .searchEqualsLabel { margin-top: 3px; - margin-right: 10px; + margin-right: 6px; +} + +svg.graphViewer text { + font: 11px Arial; + pointer-events: none; + stroke: #000; +} + +.capitalize { + text-transform:capitalize; } svg.graphViewer { border-style: groove; +} + +div.mousepointer { + position: absolute; + margin: 0; + padding: 5px; } \ No newline at end of file diff --git a/html/admin/js/graphViewer/graph/JSONAdapter.js b/html/admin/js/graphViewer/graph/JSONAdapter.js index cfa894b226..950a440706 100644 --- a/html/admin/js/graphViewer/graph/JSONAdapter.js +++ b/html/admin/js/graphViewer/graph/JSONAdapter.js @@ -162,6 +162,10 @@ function JSONAdapter(jsonPath, nodes, edges, width, height) { }; + self.setChildLimit = function (limit) { + + }; + self.expandCommunity = function (commNode, callback) { }; diff --git a/html/admin/js/graphViewer/graph/arangoAdapter.js b/html/admin/js/graphViewer/graph/arangoAdapter.js index f256b667df..343ca6d061 100644 --- a/html/admin/js/graphViewer/graph/arangoAdapter.js +++ b/html/admin/js/graphViewer/graph/arangoAdapter.js @@ -53,13 +53,16 @@ function ArangoAdapter(nodes, edges, config) { api = {}, queries = {}, cachedCommunities = {}, + joinedInCommunities = {}, nodeCollection, edgeCollection, limit, + childLimit, reducer, arangodb, width, height, + direction, setWidth = function(w) { initialX.range = w / 2; @@ -93,12 +96,22 @@ function ArangoAdapter(nodes, edges, config) { if (config.height !== undefined) { setHeight(config.height); } + if (config.undirected !== undefined) { + if (config.undirected === true) { + direction = "any"; + } else { + direction = "outbound"; + } + } else { + direction = "outbound"; + } }, findNode = function(id) { - var res = $.grep(nodes, function(e){ - return e._id === id; - }); + var intId = joinedInCommunities[id] || id, + res = $.grep(nodes, function(e){ + return e._id === intId; + }); if (res.length === 0) { return false; } @@ -145,7 +158,8 @@ function ArangoAdapter(nodes, edges, config) { _data: data, _id: data._id }, - e = findEdge(edge._id); + e = findEdge(edge._id), + edgeToPush; if (e) { return e; } @@ -160,8 +174,32 @@ function ArangoAdapter(nodes, edges, config) { edge.source = source; edge.target = target; edges.push(edge); - source._outboundCounter++; - target._inboundCounter++; + + + if (cachedCommunities[source._id] !== undefined) { + edgeToPush = {}; + edgeToPush.type = "s"; + edgeToPush.id = edge._id; + edgeToPush.source = $.grep(cachedCommunities[source._id].nodes, function(e){ + return e._id === data._from; + })[0]; + edgeToPush.source._outboundCounter++; + cachedCommunities[source._id].edges.push(edgeToPush); + } else { + source._outboundCounter++; + } + if (cachedCommunities[target._id] !== undefined) { + edgeToPush = {}; + edgeToPush.type = "t"; + edgeToPush.id = edge._id; + edgeToPush.target = $.grep(cachedCommunities[target._id].nodes, function(e){ + return e._id === data._to; + })[0]; + edgeToPush.target._inboundCounter++; + cachedCommunities[target._id].edges.push(edgeToPush); + } else { + target._inboundCounter++; + } return edge; }, @@ -275,6 +313,9 @@ function ArangoAdapter(nodes, edges, config) { if (query !== queries.connectedEdges) { bindVars["@nodes"] = nodeCollection; } + if (query !== queries.childrenCentrality) { + bindVars.dir = direction; + } bindVars["@edges"] = edgeCollection; var data = { query: query, @@ -317,8 +358,10 @@ function ArangoAdapter(nodes, edges, config) { cachedCommunities[commId] = {}; cachedCommunities[commId].nodes = nodesToRemove; cachedCommunities[commId].edges = []; + combineCommunityEdges(nodesToRemove, commNode); _.each(nodesToRemove, function(n) { + joinedInCommunities[n._id] = commId; removeNode(n); }); nodes.push(commNode); @@ -335,6 +378,7 @@ function ArangoAdapter(nodes, edges, config) { collapseCommunity(com); } _.each(nodesToAdd, function(n) { + delete joinedInCommunities[n._id]; nodes.push(n); }); _.each(edgesToChange, function(e) { @@ -358,23 +402,38 @@ function ArangoAdapter(nodes, edges, config) { parseResultOfTraversal = function (result, callback) { result = result[0]; + var inserted = {}, + n = insertNode(result[0].vertex), + com, buckets; _.each(result, function(visited) { var node = insertNode(visited.vertex), - path = visited.path; + path = visited.path; + inserted[node._id] = node; _.each(path.vertices, function(connectedNode) { - insertNode(connectedNode); + var ins = insertNode(connectedNode); + inserted[ins._id] = ins; }); _.each(path.edges, function(edge) { insertEdge(edge); }); - }); + }); + delete inserted[n._id]; + if (_.size(inserted) > childLimit) { + buckets = reducer.bucketNodes(_.values(inserted), childLimit); + _.each(buckets, function(b) { + if (b.length > 1) { + var ids = _.map(b, function(n) { + return n._id; + }); + collapseCommunity(ids); + } + }); + } + if (limit < nodes.length) { + com = reducer.getCommunity(limit, n); + collapseCommunity(com); + } if (callback) { - var n = insertNode(result[0].vertex), - com; - if (limit < nodes.length) { - com = reducer.getCommunity(limit, n); - collapseCommunity(com); - } callback(n); } }, @@ -421,7 +480,7 @@ function ArangoAdapter(nodes, edges, config) { _.each(res, self.deleteEdge); }); }; - + parseConfig(config); api.base = arangodb.lastIndexOf("http://", 0) === 0 @@ -447,7 +506,7 @@ function ArangoAdapter(nodes, edges, config) { + "@@nodes, " + "@@edges, " + "@id, " - + "\"outbound\", {" + + "@dir, {" + "strategy: \"depthfirst\"," + "maxDepth: 1," + "paths: true" @@ -460,7 +519,7 @@ function ArangoAdapter(nodes, edges, config) { + "@@nodes, " + "@@edges, " + "n._id, " - + "\"outbound\", {" + + "@dir, {" + "strategy: \"depthfirst\"," + "maxDepth: 1," + "paths: true" @@ -478,8 +537,9 @@ function ArangoAdapter(nodes, edges, config) { + " || e._from == @id" + " RETURN e"; - reducer = new NodeReducer(nodes, edges); + childLimit = Number.POSITIVE_INFINITY; + reducer = new NodeReducer(nodes, edges); self.oldLoadNodeFromTreeById = function(nodeId, callback) { sendQuery(queries.nodeById, { @@ -639,9 +699,16 @@ function ArangoAdapter(nodes, edges, config) { }); }; - self.changeTo = function (nodesCol, edgesCol ) { + self.changeTo = function (nodesCol, edgesCol, dir) { nodeCollection = nodesCol; edgeCollection = edgesCol; + if (dir !== undefined) { + if (dir === true) { + direction = "any"; + } else { + direction = "outbound"; + } + } api.node = api.base + "document?collection=" + nodeCollection; api.edge = api.base + "edge?collection=" + edgeCollection; }; @@ -657,6 +724,10 @@ function ArangoAdapter(nodes, edges, config) { } }; + self.setChildLimit = function (pLimit) { + childLimit = pLimit; + }; + self.expandCommunity = function (commNode, callback) { expandCommunity(commNode); if (callback !== undefined) { @@ -664,4 +735,34 @@ function ArangoAdapter(nodes, edges, config) { } }; + self.getCollections = function(callback) { + if (callback && callback.length >= 2) { + $.ajax({ + cache: false, + type: "GET", + url: api.collection, + contentType: "application/json", + dataType: "json", + processData: false, + success: function(data) { + var cols = data.collections, + docs = [], + edgeCols = []; + _.each(cols, function(c) { + if (!c.name.match(/^_/)) { + if (c.type === 3) { + edgeCols.push(c.name); + } else if (c.type === 2){ + docs.push(c.name); + } + } + }); + callback(docs, edgeCols); + }, + error: function(data) { + throw data.statusText; + } + }); + } + }; } \ No newline at end of file diff --git a/html/admin/js/graphViewer/graph/edgeShaper.js b/html/admin/js/graphViewer/graph/edgeShaper.js index e7b6f4f31d..1bd0a48fd6 100644 --- a/html/admin/js/graphViewer/graph/edgeShaper.js +++ b/html/admin/js/graphViewer/graph/edgeShaper.js @@ -50,7 +50,8 @@ function EdgeShaper(parent, flags, idfunc) { edges = [], toplevelSVG, visibleLabels = true, - + followEdge = {}, + followEdgeG, idFunction = function(d) { return d.source._id + "-" + d.target._id; }, @@ -70,7 +71,9 @@ function EdgeShaper(parent, flags, idfunc) { dblclick: noop, mousedown: noop, mouseup: noop, - mousemove: noop + mousemove: noop, + mouseout: noop, + mouseover: noop }; addUpdate = noop; }, @@ -317,6 +320,8 @@ function EdgeShaper(parent, flags, idfunc) { idFunction = idfunc; } + followEdgeG = toplevelSVG.append("g"); + ///////////////////////////////////////////////////////// /// Public functions @@ -349,6 +354,24 @@ function EdgeShaper(parent, flags, idfunc) { } shapeEdges(); }; + + self.addAnEdgeFollowingTheCursor = function(x, y) { + followEdge = followEdgeG.append("line"); + followEdge.attr("stroke", "black") + .attr("id", "connectionLine") + .attr("x1", x) + .attr("y1", y) + .attr("x2", x) + .attr("y2", y); + return function(x, y) { + followEdge.attr("x2", x).attr("y2", y); + }; + }; + + self.removeCursorFollowingEdge = function() { + followEdge.remove(); + followEdge = {}; + }; } EdgeShaper.shapes = Object.freeze({ diff --git a/html/admin/js/graphViewer/graph/eventDispatcher.js b/html/admin/js/graphViewer/graph/eventDispatcher.js index cc473b816f..818320008d 100644 --- a/html/admin/js/graphViewer/graph/eventDispatcher.js +++ b/html/admin/js/graphViewer/graph/eventDispatcher.js @@ -1,5 +1,5 @@ /*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ -/*global _*/ +/*global _, $, window*/ /*global EventLibrary*/ //////////////////////////////////////////////////////////////////////////////// /// @brief Graph functionality @@ -32,6 +32,9 @@ function EventDispatcher(nodeShaper, edgeShaper, config) { var eventlib, expandConfig, + svgBase, + svgTemp, + svgObj, self = this, parseNodeEditorConfig = function(config) { if (config.shaper === undefined) { @@ -77,11 +80,14 @@ function EventDispatcher(nodeShaper, edgeShaper, config) { self.events.STARTCREATEEDGE = function(callback) { return function(node) { + var e = d3.event || window.event; edgeStart = node; didInsert = false; if (callback !== undefined) { - callback(); + callback(node, e); } + // Necessary to omit dragging of the graph + e.stopPropagation(); }; }; @@ -118,6 +124,20 @@ function EventDispatcher(nodeShaper, edgeShaper, config) { }; }; } + }, + + bindSVGEvents = function() { + svgObj = svgObj || $("svg"); + _.each(svgBase, function(fs, ev) { + svgObj.bind(ev, function(trigger) { + _.each(fs, function(f) { + f(trigger); + }); + if (!! svgTemp[ev]) { + svgTemp[ev](trigger); + } + }); + }); }; if (nodeShaper === undefined) { @@ -130,6 +150,17 @@ function EventDispatcher(nodeShaper, edgeShaper, config) { eventlib = new EventLibrary(); + svgBase = { + click: [], + dblclick: [], + mousedown: [], + mouseup: [], + mousemove: [], + mouseout: [], + mouseover: [] + }; + svgTemp = {}; + self.events = {}; if (config !== undefined) { @@ -171,6 +202,10 @@ function EventDispatcher(nodeShaper, edgeShaper, config) { actions: actions }); break; + case "svg": + svgTemp[event] = func; + bindSVGEvents(); + break; default: if (object.bind !== undefined) { object.unbind(event); @@ -196,11 +231,28 @@ function EventDispatcher(nodeShaper, edgeShaper, config) { actions: actions }); break; + case "svg": + svgTemp = {}; + _.each(actions, function(fs, ev) { + if (ev !== "reset") { + svgTemp[ev] = fs; + } + }); + bindSVGEvents(); + break; default: throw "Sorry cannot rebind to object. Please give either " - + "\"nodes\" or \"edges\""; + + "\"nodes\", \"edges\" or \"svg\""; } }; + + self.fixSVG = function(event, action) { + if (svgBase[event] === undefined) { + throw "Sorry unkown event"; + } + svgBase[event].push(action); + bindSVGEvents(); + }; /* self.unbind = function () { throw "Not implemented"; diff --git a/html/admin/js/graphViewer/graph/forceLayouter.js b/html/admin/js/graphViewer/graph/forceLayouter.js index 787dc27778..34fa266339 100644 --- a/html/admin/js/graphViewer/graph/forceLayouter.js +++ b/html/admin/js/graphViewer/graph/forceLayouter.js @@ -47,9 +47,9 @@ function ForceLayouter(config) { "use strict"; var self = this, force = d3.layout.force(), - distance = config.distance || 80, - gravity = config.gravity || 0.08, - charge = config.charge || -240, + distance = config.distance || 240, // 80 + gravity = config.gravity || 0.01, // 0.08 + charge = config.charge || -1000, // -240 onUpdate = config.onUpdate || function () {}, width = config.width || 940, height = config.height || 640, diff --git a/html/admin/js/graphViewer/graph/nodeReducer.js b/html/admin/js/graphViewer/graph/nodeReducer.js index 334d3767cd..2ec7fc6983 100644 --- a/html/admin/js/graphViewer/graph/nodeReducer.js +++ b/html/admin/js/graphViewer/graph/nodeReducer.js @@ -265,9 +265,34 @@ function NodeReducer(nodes, edges) { joinCommunities(l.sID, l.lID, coms, heap, l.val); updateValues(l, dQ, a, heap); return true; + }, + + addNode = function(bucket, node) { + bucket.push(node); + + }, + + getSimilarityValue = function(bucket, node) { + if (bucket.length === 0) { + return 1; + } + var comp = bucket[0], + props = _.union(_.keys(comp), _.keys(node)), + countMatch = 0, + propCount = 0; + _.each(props, function(key) { + if (comp[key] !== undefined && node[key]!== undefined) { + countMatch++; + if (comp[key] === node[key]) { + countMatch += 4; + } + } + }); + propCount = props.length * 5; + propCount++; + countMatch++; + return countMatch / propCount; }; - - self.getCommunity = function(limit, focus) { var dQ = {}, @@ -305,4 +330,34 @@ function NodeReducer(nodes, edges) { } return res[0]; }; + + self.bucketNodes = function(toSort, numBuckets) { + var res = [], + threshold = 0.5; + if (toSort.length <= numBuckets) { + res = _.map(toSort, function(n) { + return [n]; + }); + return res; + } + _.each(toSort, function(n) { + var i, shortest, sLength; + shortest = 0; + sLength = Number.POSITIVE_INFINITY; + for (i = 0; i < numBuckets; i++) { + res[i] = res[i] || []; + if (getSimilarityValue(res[i], n) > threshold) { + addNode(res[i], n); + return; + } + if (sLength > res[i].length) { + shortest = i; + sLength = res[i].length; + } + } + addNode(res[shortest], n); + }); + return res; + }; + } diff --git a/html/admin/js/graphViewer/graph/nodeShaper.js b/html/admin/js/graphViewer/graph/nodeShaper.js index d60e7cd1cd..369b188e34 100644 --- a/html/admin/js/graphViewer/graph/nodeShaper.js +++ b/html/admin/js/graphViewer/graph/nodeShaper.js @@ -62,7 +62,7 @@ */ function NodeShaper(parent, flags, idfunc) { "use strict"; - + var self = this, communityRegEx = /^\*community/, nodes = [], @@ -114,7 +114,9 @@ function NodeShaper(parent, flags, idfunc) { drag: noop, mousedown: noop, mouseup: noop, - mousemove: noop + mousemove: noop, + mouseout: noop, + mouseover: noop }; addUpdate = noop; }, @@ -227,12 +229,14 @@ function NodeShaper(parent, flags, idfunc) { addLabel = function (node) { node.append("text") // Append a label for the node .attr("text-anchor", "middle") // Define text-anchor + .attr("stroke", "black") // Foce a black color20*75) .text(label); }; } else { addLabel = function (node) { node.append("text") // Append a label for the node .attr("text-anchor", "middle") // Define text-anchor + .attr("stroke", "black") // Foce a black color20*75) .text(function(d) { return d._data[label] !== undefined ? d._data[label] : ""; }); diff --git a/html/admin/js/graphViewer/graph/zoomManager.js b/html/admin/js/graphViewer/graph/zoomManager.js index 09a66fc933..4e2b32851d 100644 --- a/html/admin/js/graphViewer/graph/zoomManager.js +++ b/html/admin/js/graphViewer/graph/zoomManager.js @@ -105,7 +105,7 @@ function ZoomManager(width, height, svg, g, nodeShaper, edgeShaper, config, limi var fontMax = conf.maxFont || 16, fontMin = conf.minFont || 6, rMax = conf.maxRadius || 25, - rMin = conf.minRadius || 1; + rMin = conf.minRadius || 4; baseDist = conf.focusZoom || 1; baseDRadius = conf.focusRadius || 100; diff --git a/html/admin/js/graphViewer/graphViewer.js b/html/admin/js/graphViewer/graphViewer.js index 75cc74e204..38d525ba65 100644 --- a/html/admin/js/graphViewer/graphViewer.js +++ b/html/admin/js/graphViewer/graphViewer.js @@ -1,6 +1,6 @@ /*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */ /*global _*/ -/*global EventDispatcher, ArangoAdapter, JSONAdapter */ +/*global ArangoAdapter, JSONAdapter */ /*global ForceLayouter, EdgeShaper, NodeShaper, ZoomManager */ //////////////////////////////////////////////////////////////////////////////// /// @brief Graph functionality @@ -56,7 +56,6 @@ function GraphViewer(svg, width, height, adapterConfig, config) { edgeContainer, zoomManager, fixedSize, - dispatcher, edges = [], nodes = [], @@ -101,7 +100,6 @@ function GraphViewer(svg, width, height, adapterConfig, config) { nsConf = config.nodeShaper || {}, idFunc = nsConf.idfunc || undefined, zConf = config.zoom || false; - parseLayouterConfig(config.layouter); edgeContainer = graphContainer.append("g"); self.edgeShaper = new EdgeShaper(edgeContainer, esConf); @@ -113,13 +111,14 @@ function GraphViewer(svg, width, height, adapterConfig, config) { switch (adapterConfig.type.toLowerCase()) { case "arango": - adapterConfig.width = width; - adapterConfig.height = height; + adapterConfig.width = width; + adapterConfig.height = height; self.adapter = new ArangoAdapter( nodes, edges, adapterConfig ); + self.adapter.setChildLimit(5); break; case "json": self.adapter = new JSONAdapter( diff --git a/html/admin/js/graphViewer/jasmine_test/helper/eventHelper.js b/html/admin/js/graphViewer/jasmine_test/helper/eventHelper.js index 53ff15f9fc..d91e78431b 100644 --- a/html/admin/js/graphViewer/jasmine_test/helper/eventHelper.js +++ b/html/admin/js/graphViewer/jasmine_test/helper/eventHelper.js @@ -33,6 +33,14 @@ var helper = helper || {}; (function eventHelper() { "use strict"; + helper.simulateMouseMoveEvent = function(objectId, x, y) { + var evt = document.createEvent("MouseEvents"), + testee = document.getElementById(objectId); + evt.initMouseEvent("mousemove", true, true, window, + 0, 0, 0, x, y, false, false, false, false, 0, null); + testee.dispatchEvent(evt); + }; + helper.simulateDragEvent = function (objectId) { helper.simulateMouseEvent("mousedown", objectId); var e1 = document.createEvent("MouseEvents"), diff --git a/html/admin/js/graphViewer/jasmine_test/specAdapter/arangoAdapterSpec.js b/html/admin/js/graphViewer/jasmine_test/specAdapter/arangoAdapterSpec.js index 63926e00c3..f182904938 100644 --- a/html/admin/js/graphViewer/jasmine_test/specAdapter/arangoAdapterSpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specAdapter/arangoAdapterSpec.js @@ -286,6 +286,7 @@ apibase = host + "/_api/", apiCursor = apibase + 'cursor'; self.fakeReducerRequest = function() {}; + self.fakeReducerBucketRequest = function() {}; spyOn(window, "NodeReducer").andCallFake(function(v, e) { return { getCommunity: function(limit, focus) { @@ -293,6 +294,9 @@ return self.fakeReducerRequest(limit, focus); } return self.fakeReducerRequest(limit); + }, + bucketNodes: function(toSort, numBuckets) { + return self.fakeReducerBucketRequest(toSort, numBuckets); } }; }); @@ -306,26 +310,40 @@ height: 40 } ); - traversalQuery = function(id, nods, edgs) { + traversalQuery = function(id, nods, edgs, undirected) { + var dir; + if (undirected === true) { + dir = "any"; + } else { + dir = "outbound"; + } return JSON.stringify({ - query: "RETURN TRAVERSAL(@@nodes, @@edges, @id, \"outbound\"," + query: "RETURN TRAVERSAL(@@nodes, @@edges, @id, @dir," + " {strategy: \"depthfirst\",maxDepth: 1,paths: true})", bindVars: { id: id, "@nodes": nods, + dir: dir, "@edges": edgs } }); }; - filterQuery = function(v, nods, edgs) { + filterQuery = function(v, nods, edgs, undirected) { + var dir; + if (undirected === true) { + dir = "any"; + } else { + dir = "outbound"; + } return JSON.stringify({ query: "FOR n IN @@nodes FILTER n.id == @value" - + " RETURN TRAVERSAL(@@nodes, @@edges, n._id, \"outbound\"," + + " RETURN TRAVERSAL(@@nodes, @@edges, n._id, @dir," + " {strategy: \"depthfirst\",maxDepth: 1,paths: true})", bindVars: { value: v, "@nodes": nods, - "@edges": edgs + dir: dir, + "@edges": edgs } }); }; @@ -424,9 +442,45 @@ }; }; }); - + + it('should offer lists of available collections', function() { + var collections = [], + sys1 = {id: "1", name: "_sys1", status: 3, type: 2}, + sys2 = {id: "2", name: "_sys2", status: 2, type: 2}, + doc1 = {id: "3", name: "doc1", status: 3, type: 2}, + doc2 = {id: "4", name: "doc2", status: 2, type: 2}, + doc3 = {id: "5", name: "doc3", status: 3, type: 2}, + edge1 = {id: "6", name: "edge1", status: 3, type: 3}, + edge2 = {id: "7", name: "edge2", status: 2, type: 3}; + + collections.push(sys1); + collections.push(sys2); + collections.push(doc1); + collections.push(doc2); + collections.push(doc3); + collections.push(edge1); + collections.push(edge2); + + spyOn($, "ajax").andCallFake(function(request) { + request.success({collections: collections}); + }); + + adapter.getCollections(function(docs, edge) { + expect(docs).toContain("doc1"); + expect(docs).toContain("doc2"); + expect(docs).toContain("doc3"); + + expect(docs.length).toEqual(3); + + expect(edge).toContain("edge1"); + expect(edge).toContain("edge2"); + + expect(edge.length).toEqual(2); + }); + }); + it('should be able to load a tree node from ' - + 'ArangoDB by internal _id attribute', function() { + + 'ArangoDB by internal _id attribute', function() { var c0, c1, c2, c3, c4; @@ -466,8 +520,14 @@ }); }); + it('should map loadNode to loadByID', function() { + spyOn(adapter, "loadNodeFromTreeById"); + adapter.loadNode("a", "b"); + expect(adapter.loadNodeFromTreeById).toHaveBeenCalledWith("a", "b"); + }); + it('should be able to load a tree node from ArangoDB' - + ' by internal attribute and value', function() { + + ' by internal attribute and value', function() { var c0, c1, c2, c3, c4; @@ -649,6 +709,170 @@ }); + it('should be able to switch to different collections and change to directed', function() { + + runs(function() { + + spyOn($, "ajax"); + + adapter.changeTo(altNodesCollection, altEdgesCollection, false); + + adapter.loadNode("42"); + + expect($.ajax).toHaveBeenCalledWith( + requests.cursor(traversalQuery("42", altNodesCollection, altEdgesCollection, false)) + ); + + }); + }); + + it('should be able to switch to different collections' + + ' and change to undirected', function() { + + runs(function() { + + spyOn($, "ajax"); + + adapter.changeTo(altNodesCollection, altEdgesCollection, true); + + adapter.loadNode("42"); + + expect($.ajax).toHaveBeenCalledWith( + requests.cursor(traversalQuery("42", altNodesCollection, altEdgesCollection, true)) + ); + + }); + }); + + it('should add at most the upper bound of children in one step', function() { + var inNodeCol, callNodes; + + runs(function() { + var addNNodes = function(n) { + var i = 0, + res = []; + for (i = 0; i < n; i++) { + res.push(insertNode(nodesCollection, i)); + } + return res; + }, + connectToAllButSelf = function(source, ns) { + _.each(ns, function(target) { + if (source !== target) { + insertEdge(edgesCollection, source, target); + } + }); + }; + + inNodeCol = addNNodes(21); + connectToAllButSelf(inNodeCol[0], inNodeCol); + adapter.setChildLimit(5); + + spyOn($, "ajax").andCallFake(function(request) { + var vars = JSON.parse(request.data).bindVars; + if (vars !== undefined) { + request.success({result: loadGraph(vars)}); + } + }); + spyOn(this, "fakeReducerBucketRequest").andCallFake(function(ns) { + var i = 0, + res = [], + pos; + callNodes = ns; + for (i = 0; i < 5; i++) { + pos = i*4; + res.push(ns.slice(pos, pos + 4)); + } + return res; + }); + + callbackCheck = false; + adapter.loadNodeFromTreeById(inNodeCol[0], checkCallbackFunction); + + }); + + waitsFor(function() { + return callbackCheck; + }); + + runs(function() { + var callNodesIds = _.map(callNodes, function(n) { + return n._id; + }); + expect(this.fakeReducerBucketRequest).toHaveBeenCalledWith( + jasmine.any(Array), + 5 + ); + expect(callNodesIds).toEqual(inNodeCol.slice(1)); + expect(nodes.length).toEqual(6); + expect(getCommunityNodes().length).toEqual(5); + }); + + }); + + it('should not replace single nodes by communities', function() { + var inNodeCol, callNodes; + + runs(function() { + var addNNodes = function(n) { + var i = 0, + res = []; + for (i = 0; i < n; i++) { + res.push(insertNode(nodesCollection, i)); + } + return res; + }, + connectToAllButSelf = function(source, ns) { + _.each(ns, function(target) { + if (source !== target) { + insertEdge(edgesCollection, source, target); + } + }); + }; + + inNodeCol = addNNodes(7); + connectToAllButSelf(inNodeCol[0], inNodeCol); + adapter.setChildLimit(5); + + spyOn($, "ajax").andCallFake(function(request) { + var vars = JSON.parse(request.data).bindVars; + if (vars !== undefined) { + request.success({result: loadGraph(vars)}); + } + }); + spyOn(this, "fakeReducerBucketRequest").andCallFake(function(ns) { + var i = 0, + res = [], + pos; + for (i = 0; i < 4; i++) { + res.push([ns[i]]); + } + res.push([ns[4], ns[5]]); + return res; + }); + + callbackCheck = false; + adapter.loadNodeFromTreeById(inNodeCol[0], checkCallbackFunction); + + }); + + waitsFor(function() { + return callbackCheck; + }); + + runs(function() { + var callNodesIds = _.map(callNodes, function(n) { + return n._id; + }); + expect(this.fakeReducerBucketRequest).toHaveBeenCalledWith( + jasmine.any(Array), + 5 + ); + expect(nodes.length).toEqual(6); + expect(getCommunityNodes().length).toEqual(1); + }); + + }); describe('that has already loaded one graph', function() { var c0, c1, c2, c3, c4, c5, c6, c7, @@ -971,6 +1195,167 @@ }); + describe('expanding after a while', function() { + + it('should connect edges of internal nodes accordingly', function() { + + var commNode, called, counterCallback, + v0, v1, v2, v3, v4, + e0_1, e0_2, e1_3, e1_4, e2_3, e2_4; + + runs(function() { + var v = "vertices", + e = "edges"; + nodes.length = 0; + edges.length = 0; + v0 = insertNode(v, 0); + v1 = insertNode(v, 1); + v2 = insertNode(v, 2); + v3 = insertNode(v, 3); + v4 = insertNode(v, 4); + e0_1 = insertEdge(e, v0, v1); + e0_2 = insertEdge(e, v0, v2); + e1_3 = insertEdge(e, v1, v3); + e1_4 = insertEdge(e, v1, v4); + e2_3 = insertEdge(e, v2, v3); + e2_4 = insertEdge(e, v2, v4); + called = 0; + counterCallback = function() { + called++; + }; + spyOn(this, "fakeReducerRequest").andCallFake(function() { + return [v1, v3, v4]; + }); + adapter.setNodeLimit(3); + + adapter.changeTo(v, e); + adapter.loadNode(v0, counterCallback); + adapter.loadNode(v1, counterCallback); + + }); + + waitsFor(function() { + return called === 2; + }); + + runs(function() { + adapter.loadNode(v2, counterCallback); + commNode = getCommunityNodes()[0]; + }); + + waitsFor(function() { + return called === 3; + }); + + runs(function() { + var commId = commNode._id; + // Check start condition + existNodes([commId, v0, v2]); + expect(nodes.length).toEqual(3); + + existEdge(v0, v2); + existEdge(v0, commId); + existEdge(v2, commId); + expect(edges.length).toEqual(4); + + adapter.setNodeLimit(20); + adapter.expandCommunity(commNode, counterCallback); + }); + + waitsFor(function() { + return called === 4; + }); + + runs(function() { + existNodes([v0, v1, v2, v3, v4]); + expect(nodes.length).toEqual(5); + + existEdge(v0, v1); + existEdge(v0, v2); + existEdge(v1, v3); + existEdge(v1, v4); + existEdge(v2, v3); + existEdge(v2, v4); + expect(edges.length).toEqual(6); + + }); + }); + + it('set inbound and outboundcounter correctly', function() { + + var commNode, called, counterCallback, + v0, v1, v2, v3, v4, + e0_1, e0_2, e1_3, e1_4, e2_3, e2_4; + + runs(function() { + var v = "vertices", + e = "edges"; + nodes.length = 0; + edges.length = 0; + v0 = insertNode(v, 0); + v1 = insertNode(v, 1); + v2 = insertNode(v, 2); + v3 = insertNode(v, 3); + v4 = insertNode(v, 4); + e0_1 = insertEdge(e, v0, v1); + e0_2 = insertEdge(e, v0, v2); + e1_3 = insertEdge(e, v1, v3); + e1_4 = insertEdge(e, v1, v4); + e2_3 = insertEdge(e, v2, v3); + e2_4 = insertEdge(e, v2, v4); + called = 0; + counterCallback = function() { + called++; + }; + spyOn(this, "fakeReducerRequest").andCallFake(function() { + return [v1, v3, v4]; + }); + adapter.setNodeLimit(3); + + adapter.changeTo(v, e); + adapter.loadNode(v0, counterCallback); + adapter.loadNode(v1, counterCallback); + + }); + + waitsFor(function() { + return called === 2; + }); + + runs(function() { + adapter.loadNode(v2, counterCallback); + commNode = getCommunityNodes()[0]; + }); + + waitsFor(function() { + return called === 3; + }); + + runs(function() { + adapter.setNodeLimit(20); + adapter.expandCommunity(commNode, counterCallback); + }); + + waitsFor(function() { + return called === 4; + }); + + runs(function() { + var checkNodeWithInAndOut = function(id, inbound, outbound) { + var n = nodeWithID(id); + expect(n._outboundCounter).toEqual(outbound); + expect(n._inboundCounter).toEqual(inbound); + }; + checkNodeWithInAndOut(v0, 0, 2); + checkNodeWithInAndOut(v1, 1, 2); + checkNodeWithInAndOut(v2, 1, 2); + checkNodeWithInAndOut(v3, 2, 0); + checkNodeWithInAndOut(v4, 2, 0); + }); + }); + + }); + describe('that displays a community node already', function() { var firstCommId, @@ -996,7 +1381,7 @@ }); }); - it('should expand a community if enough space is available', function() { + it('should expand a community if enough space is available', function() { runs(function() { adapter.setNodeLimit(10); callbackCheck = false; @@ -1019,7 +1404,7 @@ }); it('should expand a community and join another ' - + 'one if not enough space is available', function() { + + 'one if not enough space is available', function() { runs(function() { fakeResult = [c1, c7]; callbackCheck = false; @@ -1079,6 +1464,26 @@ }); }); + it('should connect edges to internal nodes', function() { + + runs(function() { + insertEdge(edgesCollection, c3, c0); + + adapter.setNodeLimit(20); + callbackCheck = false; + adapter.loadNode(c3, checkCallbackFunction); + }); + + waitsFor(function() { + return callbackCheck; + }); + + runs(function() { + existEdge(c3, firstCommId); + }); + + }); + }); }); diff --git a/html/admin/js/graphViewer/jasmine_test/specAdapter/arangoAdapterUISpec.js b/html/admin/js/graphViewer/jasmine_test/specAdapter/arangoAdapterUISpec.js index 206283ac69..9da406b57c 100644 --- a/html/admin/js/graphViewer/jasmine_test/specAdapter/arangoAdapterUISpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specAdapter/arangoAdapterUISpec.js @@ -43,13 +43,17 @@ beforeEach(function () { adapter = { - changeTo: function(){} + changeTo: function(){}, + getCollections: function(cb) { + cb(["nodes", "newNodes"], ["edges", "newEdges"]); + } }; list = document.createElement("ul"); document.body.appendChild(list); - list.id = "control_list"; + list.id = "control_adapter_list"; adapterUI = new ArangoAdapterControls(list, adapter); spyOn(adapter, 'changeTo'); + spyOn(adapter, "getCollections").andCallThrough(); this.addMatchers({ toBeTag: function(name) { var el = this.actual; @@ -88,34 +92,119 @@ runs(function() { adapterUI.addControlChangeCollections(); - expect($("#control_list #control_collections").length).toEqual(1); - expect($("#control_list #control_collections")[0]).toConformToListCSS(); + expect($("#control_adapter_list #control_adapter_collections").length).toEqual(1); + expect($("#control_adapter_list #control_adapter_collections")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_collections"); + helper.simulateMouseEvent("click", "control_adapter_collections"); - expect($("#control_collections_modal").length).toEqual(1); + expect($("#control_adapter_collections_modal").length).toEqual(1); - $("#control_collections_nodecollection").attr("value", "newNodes"); - $("#control_collections_edgecollection").attr("value", "newEdges"); + $("#control_adapter_collections_nodecollection").attr("value", "newNodes"); + $("#control_adapter_collections_edgecollection").attr("value", "newEdges"); - helper.simulateMouseEvent("click", "control_collections_submit"); + helper.simulateMouseEvent("click", "control_adapter_collections_submit"); expect(adapter.changeTo).toHaveBeenCalledWith( "newNodes", - "newEdges" + "newEdges", + false ); }); waitsFor(function() { - return $("#control_collections_modal").length === 0; + return $("#control_adapter_collections_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); + it('should change collections and traversal direction to directed', function() { + runs(function() { + adapterUI.addControlChangeCollections(); + helper.simulateMouseEvent("click", "control_adapter_collections"); + + $("#control_adapter_collections_nodecollection").attr("value", "newNodes"); + $("#control_adapter_collections_edgecollection").attr("value", "newEdges"); + $("#control_adapter_collections_undirected").attr("checked", false); + + helper.simulateMouseEvent("click", "control_adapter_collections_submit"); + + expect(adapter.changeTo).toHaveBeenCalledWith( + "newNodes", + "newEdges", + false + ); + + }); + + waitsFor(function() { + return $("#control_adapter_collections_modal").length === 0; + }, 2000, "The modal dialog should disappear."); + + }); + + it('should change collections and traversal direction to undirected', function() { + runs(function() { + adapterUI.addControlChangeCollections(); + helper.simulateMouseEvent("click", "control_adapter_collections"); + $("#control_adapter_collections_nodecollection").attr("value", "newNodes"); + $("#control_adapter_collections_edgecollection").attr("value", "newEdges"); + $("#control_adapter_collections_undirected").attr("checked", true); + + helper.simulateMouseEvent("click", "control_adapter_collections_submit"); + + expect(adapter.changeTo).toHaveBeenCalledWith( + "newNodes", + "newEdges", + true + ); + + }); + + waitsFor(function() { + return $("#control_adapter_collections_modal").length === 0; + }, 2000, "The modal dialog should disappear."); + + }); + + it('should offer the available collections as lists', function() { + runs(function() { + adapterUI.addControlChangeCollections(); + + helper.simulateMouseEvent("click", "control_adapter_collections"); + var docList = document.getElementById("control_adapter_collections_nodecollection"), + edgeList = document.getElementById("control_adapter_collections_edgecollection"), + docCollectionOptions = docList.children, + edgeCollectionOptions = edgeList.children; + + expect(adapter.getCollections).toHaveBeenCalled(); + + expect(docList).toBeTag("select"); + expect(docCollectionOptions.length).toEqual(2); + expect(docCollectionOptions[0]).toBeTag("option"); + expect(docCollectionOptions[1]).toBeTag("option"); + expect(docCollectionOptions[0].value).toEqual("nodes"); + expect(docCollectionOptions[1].value).toEqual("newNodes"); + + + expect(edgeList).toBeTag("select"); + expect(edgeCollectionOptions.length).toEqual(2); + expect(edgeCollectionOptions[0]).toBeTag("option"); + expect(edgeCollectionOptions[1]).toBeTag("option"); + expect(edgeCollectionOptions[0].value).toEqual("edges"); + expect(edgeCollectionOptions[1].value).toEqual("newEdges"); + + helper.simulateMouseEvent("click", "control_adapter_collections_submit"); + }); + + waitsFor(function() { + return $("#control_adapter_collections_modal").length === 0; + }, 2000, "The modal dialog should disappear."); + }); + it('should be able to add all controls to the list', function() { adapterUI.addAll(); - expect($("#control_list #control_collections").length).toEqual(1); + expect($("#control_adapter_list #control_adapter_collections").length).toEqual(1); }); }); }()); \ No newline at end of file diff --git a/html/admin/js/graphViewer/jasmine_test/specAdapter/interfaceSpec.js b/html/admin/js/graphViewer/jasmine_test/specAdapter/interfaceSpec.js index 485dd09d1b..299302a30f 100644 --- a/html/admin/js/graphViewer/jasmine_test/specAdapter/interfaceSpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specAdapter/interfaceSpec.js @@ -67,7 +67,9 @@ var describeInterface = function (testee) { expect(testee).toHaveFunction("deleteNode", 2); expect(testee).toHaveFunction("patchNode", 3); expect(testee).toHaveFunction("setNodeLimit", 2); + expect(testee).toHaveFunction("setChildLimit", 1); expect(testee).toHaveFunction("expandCommunity", 2); + }); }; diff --git a/html/admin/js/graphViewer/jasmine_test/specEdgeShaper/edgeShaperSpec.js b/html/admin/js/graphViewer/jasmine_test/specEdgeShaper/edgeShaperSpec.js index e058f8e6be..2b01b81aec 100644 --- a/html/admin/js/graphViewer/jasmine_test/specEdgeShaper/edgeShaperSpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specEdgeShaper/edgeShaperSpec.js @@ -42,6 +42,7 @@ beforeEach(function () { svg = document.createElement("svg"); + svg.id = "svg"; document.body.appendChild(svg); }); @@ -274,6 +275,55 @@ expect($("#1-5 line").attr("x2")).toEqual("28.284271247461902"); }); + it('should be able to draw an edge that follows the cursor', function() { + var line, + jqLine, + cursorX, + cursorY, + nodeX = 15, + nodeY = 20, + shaper = new EdgeShaper(d3.select("svg")), + moveCB = shaper.addAnEdgeFollowingTheCursor(nodeX, nodeY); + + cursorX = 20; + cursorY = 30; + moveCB(cursorX, cursorY); + + expect($("#connectionLine").length).toEqual(1); + + jqLine = $("#connectionLine"); + line = document.getElementById("connectionLine"); + expect(line.tagName.toLowerCase()).toEqual("line"); + expect(jqLine.attr("x1")).toEqual(String(nodeX)); + expect(jqLine.attr("y1")).toEqual(String(nodeY)); + + expect(jqLine.attr("x2")).toEqual(String(cursorX)); + expect(jqLine.attr("y2")).toEqual(String(cursorY)); + + cursorX = 45; + cursorY = 12; + moveCB(cursorX, cursorY); + expect(jqLine.attr("x2")).toEqual(String(cursorX)); + expect(jqLine.attr("y2")).toEqual(String(cursorY)); + }); + + it('should be able to remove the cursor-following edge on demand', function() { + var line, + cursorX, + cursorY, + nodeX = 15, + nodeY = 20, + shaper = new EdgeShaper(d3.select("svg")), + moveCB; + + moveCB = shaper.addAnEdgeFollowingTheCursor(nodeX, nodeY); + cursorX = 20; + cursorY = 30; + moveCB(cursorX, cursorY); + shaper.removeCursorFollowingEdge(); + expect($("#connectionLine").length).toEqual(0); + }); + describe('testing for colours', function() { it('should have a default colouring of no colour flag is given', function() { diff --git a/html/admin/js/graphViewer/jasmine_test/specEdgeShaper/edgeShaperUISpec.js b/html/admin/js/graphViewer/jasmine_test/specEdgeShaper/edgeShaperUISpec.js index 3508071a69..f1ac9f8598 100644 --- a/html/admin/js/graphViewer/jasmine_test/specEdgeShaper/edgeShaperUISpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specEdgeShaper/edgeShaperUISpec.js @@ -47,7 +47,7 @@ shaper = new EdgeShaper(d3.select("svg")); list = document.createElement("ul"); document.body.appendChild(list); - list.id = "control_list"; + list.id = "control_edge_list"; shaperUI = new EdgeShaperControls(list, shaper); spyOn(shaper, 'changeTo'); this.addMatchers({ @@ -94,10 +94,10 @@ runs(function() { shaperUI.addControlOpticShapeNone(); - expect($("#control_list #control_none").length).toEqual(1); - expect($("#control_list #control_none")[0]).toConformToListCSS(); + expect($("#control_edge_list #control_edge_none").length).toEqual(1); + expect($("#control_edge_list #control_edge_none")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_none"); + helper.simulateMouseEvent("click", "control_edge_none"); expect(shaper.changeTo).toHaveBeenCalledWith({ shape: { @@ -111,10 +111,10 @@ runs(function() { shaperUI.addControlOpticShapeArrow(); - expect($("#control_list #control_arrow").length).toEqual(1); - expect($("#control_list #control_arrow")[0]).toConformToListCSS(); + expect($("#control_edge_list #control_edge_arrow").length).toEqual(1); + expect($("#control_edge_list #control_edge_arrow")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_arrow"); + helper.simulateMouseEvent("click", "control_edge_arrow"); expect(shaper.changeTo).toHaveBeenCalledWith({ shape: { @@ -129,12 +129,12 @@ runs(function() { shaperUI.addControlOpticLabel(); - expect($("#control_list #control_label").length).toEqual(1); - expect($("#control_list #control_label")[0]).toConformToListCSS(); + expect($("#control_edge_list #control_edge_label").length).toEqual(1); + expect($("#control_edge_list #control_edge_label")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_label"); - $("#control_label_key").attr("value", "theAnswer"); - helper.simulateMouseEvent("click", "control_label_submit"); + helper.simulateMouseEvent("click", "control_edge_label"); + $("#control_edge_label_key").attr("value", "theAnswer"); + helper.simulateMouseEvent("click", "control_edge_label_submit"); expect(shaper.changeTo).toHaveBeenCalledWith({ label: "theAnswer" @@ -142,7 +142,7 @@ }); waitsFor(function() { - return $("#control_label_modal").length === 0; + return $("#control_edge_label_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); @@ -151,12 +151,12 @@ runs(function() { shaperUI.addControlOpticSingleColour(); - expect($("#control_list #control_singlecolour").length).toEqual(1); - expect($("#control_list #control_singlecolour")[0]).toConformToListCSS(); + expect($("#control_edge_list #control_edge_singlecolour").length).toEqual(1); + expect($("#control_edge_list #control_edge_singlecolour")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_singlecolour"); - $("#control_singlecolour_stroke").attr("value", "#123456"); - helper.simulateMouseEvent("click", "control_singlecolour_submit"); + helper.simulateMouseEvent("click", "control_edge_singlecolour"); + $("#control_edge_singlecolour_stroke").attr("value", "#123456"); + helper.simulateMouseEvent("click", "control_edge_singlecolour_submit"); expect(shaper.changeTo).toHaveBeenCalledWith({ color: { @@ -167,7 +167,7 @@ }); waitsFor(function() { - return $("#control_singlecolour_modal").length === 0; + return $("#control_edge_singlecolour_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); @@ -176,12 +176,12 @@ runs(function() { shaperUI.addControlOpticAttributeColour(); - expect($("#control_list #control_attributecolour").length).toEqual(1); - expect($("#control_list #control_attributecolour")[0]).toConformToListCSS(); + expect($("#control_edge_list #control_edge_attributecolour").length).toEqual(1); + expect($("#control_edge_list #control_edge_attributecolour")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_attributecolour"); - $("#control_attributecolour_key").attr("value", "label"); - helper.simulateMouseEvent("click", "control_attributecolour_submit"); + helper.simulateMouseEvent("click", "control_edge_attributecolour"); + $("#control_edge_attributecolour_key").attr("value", "label"); + helper.simulateMouseEvent("click", "control_edge_attributecolour_submit"); expect(shaper.changeTo).toHaveBeenCalledWith({ color: { @@ -192,7 +192,7 @@ }); waitsFor(function() { - return $("#control_attributecolour_modal").length === 0; + return $("#control_edge_attributecolour_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); @@ -201,13 +201,13 @@ runs(function() { shaperUI.addControlOpticGradientColour(); - expect($("#control_list #control_gradientcolour").length).toEqual(1); - expect($("#control_list #control_gradientcolour")[0]).toConformToListCSS(); + expect($("#control_edge_list #control_edge_gradientcolour").length).toEqual(1); + expect($("#control_edge_list #control_edge_gradientcolour")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_gradientcolour"); - $("#control_gradientcolour_source").attr("value", "#123456"); - $("#control_gradientcolour_target").attr("value", "#654321"); - helper.simulateMouseEvent("click", "control_gradientcolour_submit"); + helper.simulateMouseEvent("click", "control_edge_gradientcolour"); + $("#control_edge_gradientcolour_source").attr("value", "#123456"); + $("#control_edge_gradientcolour_target").attr("value", "#654321"); + helper.simulateMouseEvent("click", "control_edge_gradientcolour_submit"); expect(shaper.changeTo).toHaveBeenCalledWith({ color: { @@ -219,7 +219,7 @@ }); waitsFor(function() { - return $("#control_gradientcolour_modal").length === 0; + return $("#control_edge_gradientcolour_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); @@ -227,12 +227,12 @@ it('should be able to add all optic controls to the list', function () { shaperUI.addAllOptics(); - expect($("#control_list #control_none").length).toEqual(1); - expect($("#control_list #control_arrow").length).toEqual(1); - expect($("#control_list #control_label").length).toEqual(1); - expect($("#control_list #control_singlecolour").length).toEqual(1); - expect($("#control_list #control_attributecolour").length).toEqual(1); - expect($("#control_list #control_gradientcolour").length).toEqual(1); + expect($("#control_edge_list #control_edge_none").length).toEqual(1); + expect($("#control_edge_list #control_edge_arrow").length).toEqual(1); + expect($("#control_edge_list #control_edge_label").length).toEqual(1); + expect($("#control_edge_list #control_edge_singlecolour").length).toEqual(1); + expect($("#control_edge_list #control_edge_attributecolour").length).toEqual(1); + expect($("#control_edge_list #control_edge_gradientcolour").length).toEqual(1); }); @@ -244,12 +244,12 @@ it('should be able to add all controls to the list', function () { shaperUI.addAll(); - expect($("#control_list #control_none").length).toEqual(1); - expect($("#control_list #control_arrow").length).toEqual(1); - expect($("#control_list #control_label").length).toEqual(1); - expect($("#control_list #control_singlecolour").length).toEqual(1); - expect($("#control_list #control_attributecolour").length).toEqual(1); - expect($("#control_list #control_gradientcolour").length).toEqual(1); + expect($("#control_edge_list #control_edge_none").length).toEqual(1); + expect($("#control_edge_list #control_edge_arrow").length).toEqual(1); + expect($("#control_edge_list #control_edge_label").length).toEqual(1); + expect($("#control_edge_list #control_edge_singlecolour").length).toEqual(1); + expect($("#control_edge_list #control_edge_attributecolour").length).toEqual(1); + expect($("#control_edge_list #control_edge_gradientcolour").length).toEqual(1); }); }); diff --git a/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherSpec.js b/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherSpec.js index ac7bd7ee46..3d9a6d0b25 100644 --- a/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherSpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherSpec.js @@ -759,6 +759,147 @@ }); }); + }); + + describe('checking binding to SVG', function() { + + it('should be able to permanently bind many functions to events', function() { + var functions = { + a: function() {}, + b: function() {}, + c: function() {}, + d: function() {} + }; + spyOn(functions, "a"); + spyOn(functions, "b"); + spyOn(functions, "c"); + spyOn(functions, "d"); + + dispatcher.fixSVG("click", functions.a); + dispatcher.fixSVG("click", functions.b); + dispatcher.fixSVG("click", functions.c); + dispatcher.fixSVG("click", functions.d); + + helper.simulateMouseEvent("click", "svg"); + + expect(functions.a).wasCalled(); + expect(functions.b).wasCalled(); + expect(functions.c).wasCalled(); + expect(functions.d).wasCalled(); + }); + + it('should be able to bind temporary functions to events', function() { + var functions = { + a: function() {} + }; + spyOn(functions, "a"); + + dispatcher.bind("svg", "click", functions.a); + + helper.simulateMouseEvent("click", "svg"); + + expect(functions.a).wasCalled(); + }); + + it('should be able to overwrite temporary functions on events', function() { + var functions = { + a: function() {}, + b: function() {} + }; + spyOn(functions, "a"); + spyOn(functions, "b"); + + dispatcher.bind("svg", "click", functions.a); + + dispatcher.bind("svg", "click", functions.b); + + helper.simulateMouseEvent("click", "svg"); + + expect(functions.b).wasCalled(); + expect(functions.a).wasNotCalled(); + }); + + it('should not overwrite permanent functions', function() { + var functions = { + a: function() {}, + b: function() {} + }; + spyOn(functions, "a"); + spyOn(functions, "b"); + + dispatcher.fixSVG("click", functions.a); + + dispatcher.bind("svg", "click", functions.b); + + helper.simulateMouseEvent("click", "svg"); + + expect(functions.b).wasCalled(); + expect(functions.a).wasCalled(); + }); + + it('binding a permanent function should not effect temporary function', function() { + var functions = { + a: function() {}, + b: function() {} + }; + spyOn(functions, "a"); + spyOn(functions, "b"); + + dispatcher.bind("svg", "click", functions.b); + dispatcher.fixSVG("click", functions.a); + + helper.simulateMouseEvent("click", "svg"); + + expect(functions.b).wasCalled(); + expect(functions.a).wasCalled(); + }); + + it('should be able to bind only the given events and' + + ' unbind other temporary events', function() { + var functions = { + a: function() {}, + b: function() {}, + c: function() {}, + d: function() {} + }; + spyOn(functions, "a"); + spyOn(functions, "b"); + spyOn(functions, "c"); + spyOn(functions, "d"); + + dispatcher.bind("svg", "mouseup", functions.a); + dispatcher.bind("svg", "mousedown", functions.b); + dispatcher.bind("svg", "click", functions.c); + dispatcher.rebind("svg", {click: functions.d}); + + helper.simulateMouseEvent("click", "svg"); + helper.simulateMouseEvent("mouseup", "svg"); + helper.simulateMouseEvent("mousedown", "svg"); + + expect(functions.a).wasNotCalled(); + expect(functions.b).wasNotCalled(); + expect(functions.c).wasNotCalled(); + expect(functions.d).wasCalled(); + }); + + it('should not remove permanent events on rebind', function() { + var functions = { + a: function() {}, + b: function() {} + }; + spyOn(functions, "a"); + spyOn(functions, "b"); + + dispatcher.fixSVG("mousemove", functions.a); + dispatcher.rebind("svg", {click: functions.b}); + + helper.simulateMouseEvent("mousemove", "svg"); + helper.simulateMouseEvent("click", "svg"); + + expect(functions.a).wasCalled(); + expect(functions.b).wasCalled(); + }); + }); describe('checking rebinding of events', function() { @@ -843,6 +984,8 @@ }); }); + + }); }); diff --git a/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherUISpec.js b/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherUISpec.js index e641dccb45..f10ad66e0d 100644 --- a/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherUISpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specEvents/eventDispatcherUISpec.js @@ -42,6 +42,7 @@ var svg, dispatcher, dispatcherUI, list, nodeShaper, edgeShaper, layouter, nodes, edges, adapter, + mousePointerbox, addSpies = function() { spyOn(layouter, "drag"); @@ -60,17 +61,21 @@ beforeEach(function () { nodes = [{ _id: 1, - _rev: 1, - _key: 1, + x: 3, + y: 4, _data: { _id: 1, + _rev: 1, + _key: 1, name: "Alice" } },{ _id: 2, - _rev: 2, - _key: 2, + x: 1, + y: 2, _data: { + _rev: 2, + _key: 2, _id: 2 } }]; @@ -121,16 +126,26 @@ }; svg = document.createElement("svg"); + svg.id = "svg"; document.body.appendChild(svg); nodeShaper = new NodeShaper(d3.select("svg")); edgeShaper = new EdgeShaper(d3.select("svg")); list = document.createElement("ul"); document.body.appendChild(list); - list.id = "control_list"; + list.id = "control_event_list"; + + mousePointerbox = document.createElement("svg"); + mousePointerbox.id = "mousepointer"; + mousePointerbox.className = "mousepointer"; + + document.body.appendChild(mousePointerbox); + nodeShaper.drawNodes(nodes); edgeShaper.drawEdges(edges); - dispatcherUI = new EventDispatcherControls(list, nodeShaper, edgeShaper, completeConfig); + dispatcherUI = new EventDispatcherControls( + list, mousePointerbox, nodeShaper, edgeShaper, completeConfig + ); spyOn(nodeShaper, "changeTo").andCallThrough(); spyOn(edgeShaper, "changeTo").andCallThrough(); @@ -174,6 +189,7 @@ expect(list).toConformToToolbox(); document.body.removeChild(list); document.body.removeChild(svg); + document.body.removeChild(mousePointerbox); }); it('should throw errors if not setup correctly', function() { @@ -182,9 +198,12 @@ }).toThrow("A list element has to be given."); expect(function() { var e = new EventDispatcherControls(list); + }).toThrow("The cursor decoration box has to be given."); + expect(function() { + var e = new EventDispatcherControls(list, mousePointerbox); }).toThrow("The NodeShaper has to be given."); expect(function() { - var e = new EventDispatcherControls(list, nodeShaper); + var e = new EventDispatcherControls(list, mousePointerbox, nodeShaper); }).toThrow("The EdgeShaper has to be given."); }); @@ -192,9 +211,9 @@ runs(function() { dispatcherUI.addControlDrag(); - expect($("#control_list #control_drag").length).toEqual(1); + expect($("#control_event_list #control_event_drag").length).toEqual(1); - helper.simulateMouseEvent("click", "control_drag"); + helper.simulateMouseEvent("click", "control_event_drag"); expect(nodeShaper.changeTo).toHaveBeenCalledWith({ actions: { @@ -209,6 +228,8 @@ } }); + expect(mousePointerbox.className).toEqual("mousepointer icon-move"); + helper.simulateDragEvent("1"); expect(layouter.drag).toHaveBeenCalled(); @@ -220,9 +241,9 @@ runs(function() { dispatcherUI.addControlEdit(); - expect($("#control_list #control_edit").length).toEqual(1); + expect($("#control_event_list #control_event_edit").length).toEqual(1); - helper.simulateMouseEvent("click", "control_edit"); + helper.simulateMouseEvent("click", "control_event_edit"); expect(nodeShaper.changeTo).toHaveBeenCalledWith({ actions: { @@ -238,39 +259,38 @@ } }); + expect(mousePointerbox.className).toEqual("mousepointer icon-pencil"); + helper.simulateMouseEvent("click", "1"); - expect($("#control_node_edit_modal").length).toEqual(1); + expect($("#control_event_node_edit_modal").length).toEqual(1); - $("#control_node_edit_name_value").val("Bob"); + $("#control_event_node_edit_name_value").val("Bob"); - helper.simulateMouseEvent("click", "control_node_edit_submit"); + helper.simulateMouseEvent("click", "control_event_node_edit_submit"); expect(adapter.patchNode).toHaveBeenCalledWith( nodes[0], - { _id: "1", + { name: "Bob" }, jasmine.any(Function)); }); waitsFor(function() { - return $("#control_node_edit_modal").length === 0; + return $("#control_event_node_edit_modal").length === 0; }, 2000, "The modal dialog should disappear."); runs(function() { helper.simulateMouseEvent("click", "1-2"); - expect($("#control_edge_edit_modal").length).toEqual(1); + expect($("#control_event_edge_edit_modal").length).toEqual(1); - $("#control_edge_edit_label_value").val("newLabel"); - helper.simulateMouseEvent("click", "control_edge_edit_submit"); + $("#control_event_edge_edit_label_value").val("newLabel"); + helper.simulateMouseEvent("click", "control_event_edge_edit_submit"); expect(adapter.patchEdge).toHaveBeenCalledWith( edges[0], { - _id: "12", - _rev: "12", - _key: "12", _from: "1", _to: "2", label: "newLabel" @@ -279,7 +299,7 @@ }); waitsFor(function() { - return $("#control_edge_edit_modal").length === 0; + return $("#control_event_edge_edit_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); @@ -288,9 +308,9 @@ runs(function() { dispatcherUI.addControlExpand(); - expect($("#control_list #control_expand").length).toEqual(1); + expect($("#control_event_list #control_event_expand").length).toEqual(1); - helper.simulateMouseEvent("click", "control_expand"); + helper.simulateMouseEvent("click", "control_event_expand"); expect(nodeShaper.changeTo).toHaveBeenCalledWith({ actions: { @@ -305,6 +325,8 @@ } }); + expect(mousePointerbox.className).toEqual("mousepointer icon-plus"); + helper.simulateMouseEvent("click", "1"); expect(adapter.loadNode).toHaveBeenCalledWith(nodes[0]._id, jasmine.any(Function)); @@ -316,9 +338,9 @@ runs(function() { dispatcherUI.addControlDelete(); - expect($("#control_list #control_delete").length).toEqual(1); + expect($("#control_event_list #control_event_delete").length).toEqual(1); - helper.simulateMouseEvent("click", "control_delete"); + helper.simulateMouseEvent("click", "control_event_delete"); expect(edgeShaper.changeTo).toHaveBeenCalledWith({ actions: { @@ -334,6 +356,8 @@ } }); + expect(mousePointerbox.className).toEqual("mousepointer icon-trash"); + helper.simulateMouseEvent("click", "1"); expect(adapter.deleteNode).toHaveBeenCalledWith( @@ -351,48 +375,104 @@ }); }); - it('should be able to add a connect control to the list', function() { - runs(function() { + describe('the connect control', function() { + + it('should be added to the list', function() { + runs(function() { + dispatcherUI.addControlConnect(); + + expect($("#control_event_list #control_event_connect").length).toEqual(1); + + helper.simulateMouseEvent("click", "control_event_connect"); + + expect(nodeShaper.changeTo).toHaveBeenCalledWith({ + actions: { + reset: true, + mousedown: jasmine.any(Function), + mouseup: jasmine.any(Function) + } + }); + + expect(edgeShaper.changeTo).toHaveBeenCalledWith({ + actions: { + reset: true + } + }); + + expect(mousePointerbox.className).toEqual("mousepointer icon-resize-horizontal"); + + helper.simulateMouseEvent("mousedown", "2"); + + helper.simulateMouseEvent("mouseup", "1"); + + expect(adapter.createEdge).toHaveBeenCalledWith( + {source: nodes[1], target: nodes[0]}, + jasmine.any(Function) + ); + + }); + }); + + it('should draw a line from startNode following the cursor', function() { + var line, + cursorX, + cursorY; + + spyOn(edgeShaper, "addAnEdgeFollowingTheCursor"); + dispatcherUI.addControlConnect(); - - expect($("#control_list #control_connect").length).toEqual(1); - - helper.simulateMouseEvent("click", "control_connect"); - - expect(nodeShaper.changeTo).toHaveBeenCalledWith({ - actions: { - reset: true, - mousedown: jasmine.any(Function), - mouseup: jasmine.any(Function) - } - }); - - expect(edgeShaper.changeTo).toHaveBeenCalledWith({ - actions: { - reset: true - } - }); - + helper.simulateMouseEvent("click", "control_event_connect"); helper.simulateMouseEvent("mousedown", "2"); - helper.simulateMouseEvent("mouseup", "1"); - - expect(adapter.createEdge).toHaveBeenCalledWith( - {source: nodes[1], target: nodes[0]}, - jasmine.any(Function) + expect(edgeShaper.addAnEdgeFollowingTheCursor).toHaveBeenCalledWith( + 0, 0 ); + }); + + it('the cursor-line should follow the cursor on mousemove over svg', function() { + dispatcherUI.addControlConnect(); + helper.simulateMouseEvent("click", "control_event_connect"); + helper.simulateMouseEvent("mousedown", "2"); - }); + helper.simulateMouseMoveEvent("svg", 40, 50); + + var line = $("#connectionLine"); + expect(line.attr("x1")).toEqual(String(nodes[1].x)); + expect(line.attr("y1")).toEqual(String(nodes[1].y)); + expect(line.attr("x2")).toEqual("40"); + expect(line.attr("y2")).toEqual("50"); + }); + + it('the cursor-line should disappear on mouseup on svg', function() { + spyOn(edgeShaper, "removeCursorFollowingEdge"); + dispatcherUI.addControlConnect(); + helper.simulateMouseEvent("click", "control_event_connect"); + helper.simulateMouseEvent("mousedown", "2"); + helper.simulateMouseEvent("mouseup", "1-2"); + expect(edgeShaper.removeCursorFollowingEdge).toHaveBeenCalled(); + }); + + it('the cursor-line should disappear on mouseup on svg', function() { + spyOn(edgeShaper, "removeCursorFollowingEdge"); + dispatcherUI.addControlConnect(); + helper.simulateMouseEvent("click", "control_event_connect"); + helper.simulateMouseEvent("mousedown", "2"); + helper.simulateMouseEvent("mouseup", "1"); + expect(edgeShaper.removeCursorFollowingEdge).toHaveBeenCalled(); + }); + }); + + it('should be able to add all controls to the list', function () { dispatcherUI.addAll(); - expect($("#control_list #control_drag").length).toEqual(1); - expect($("#control_list #control_edit").length).toEqual(1); - expect($("#control_list #control_expand").length).toEqual(1); - expect($("#control_list #control_delete").length).toEqual(1); - expect($("#control_list #control_connect").length).toEqual(1); + expect($("#control_event_list #control_event_drag").length).toEqual(1); + expect($("#control_event_list #control_event_edit").length).toEqual(1); + expect($("#control_event_list #control_event_expand").length).toEqual(1); + expect($("#control_event_list #control_event_delete").length).toEqual(1); + expect($("#control_event_list #control_event_connect").length).toEqual(1); }); }); diff --git a/html/admin/js/graphViewer/jasmine_test/specEvents/eventLibrarySpec.js b/html/admin/js/graphViewer/jasmine_test/specEvents/eventLibrarySpec.js index a87de74c62..6a39dc7b7e 100644 --- a/html/admin/js/graphViewer/jasmine_test/specEvents/eventLibrarySpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specEvents/eventLibrarySpec.js @@ -209,21 +209,127 @@ expect(c2._outboundCounter).toEqual(1); }); - it('should expand a community node properly', function() { - var comm = { - _id: "*community_1" - }; - nodes.push(comm); + describe('with community nodes', function() { - spyOn(adapterDummy, "expandCommunity"); + it('should expand a community node properly', function() { + var comm = { + _id: "*community_1" + }; + nodes.push(comm); - testee = eventLib.Expand(config); - testee(comm); + spyOn(adapterDummy, "expandCommunity"); + + testee = eventLib.Expand(config); + testee(comm); + + expect(adapterDummy.expandCommunity).toHaveBeenCalledWith(comm, jasmine.any(Function)); + }); + + it('should remove a community if last pointer to it is collapsed', function() { + + runs(function() { + var c0 = { + _id: 0, + _outboundCounter: 1, + _inboundCounter: 0 + }, + c1 = { + _id: 1, + _expanded: true, + _outboundCounter: 1, + _inboundCounter: 1 + }, + comm = { + _id: "*community_1", + _outboundCounter: 1, + _inboundCounter: 1 + }, + c2 = { + _id: 1, + _outboundCounter: 0, + _inboundCounter: 1 + }, + e0 = { + source: c0, + target: c1 + }, + e1 = { + source: c1, + target: comm + }, + e2 = { + source: comm, + target: c2 + }; + nodes.push(c0); + nodes.push(c1); + nodes.push(comm); + nodes.push(c2); + edges.push(e0); + edges.push(e1); + edges.push(e2); + + testee = eventLib.Expand(config); + testee(c1); + + expect(nodes).toEqual([c0, c1]); + expect(edges).toEqual([e0]); + }); + + }); + + it('should not remove a community if a pointer to it still exists', function() { + + runs(function() { + var c0 = { + _id: 0, + _outboundCounter: 2, + _inboundCounter: 0 + }, + c1 = { + _id: 1, + _expanded: true, + _outboundCounter: 1, + _inboundCounter: 1 + }, + comm = { + _id: "*community_1", + _outboundCounter: 0, + _inboundCounter: 2 + }, + e0 = { + source: c0, + target: c1 + }, + e1 = { + source: c0, + target: comm + }, + e2 = { + source: c1, + target: comm + }; + nodes.push(c0); + nodes.push(c1); + nodes.push(comm); + edges.push(e0); + edges.push(e1); + edges.push(e2); + + testee = eventLib.Expand(config); + testee(c1); + + expect(nodes).toEqual([c0, c1, comm]); + expect(edges).toEqual([e0, e1]); + }); + + }); - expect(adapterDummy.expandCommunity).toHaveBeenCalledWith(comm, jasmine.any(Function)); }); + + describe('setup process', function() { var testConfig = {}; diff --git a/html/admin/js/graphViewer/jasmine_test/specForceLayouter/forceLayouterSpec.js b/html/admin/js/graphViewer/jasmine_test/specForceLayouter/forceLayouterSpec.js index ad9882b29d..57cf900cb9 100644 --- a/html/admin/js/graphViewer/jasmine_test/specForceLayouter/forceLayouterSpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specForceLayouter/forceLayouterSpec.js @@ -154,7 +154,7 @@ + " should be close to Node " + n2.id + " but distance is " + distance; }; - threshold = threshold || 100; + threshold = threshold || 150; return Math.abs(distance) < threshold; }, @@ -241,7 +241,7 @@ }); it('should position not linked nodes close to each other', function() { - runs(function() { + runs(function() { nodes = createNodeList(4); standardConfig.nodes = nodes; edgeShaper = {"updateEdges": function(){}}; @@ -391,10 +391,16 @@ var tmp = new ForceLayouter(config); + /* expect(mock.size).wasCalledWith([940, 640]); - expect(mock.linkDistance).wasCalledWith(80); + expect(mock.linkDistance).wasCalledWith(240); expect(mock.gravity).wasCalledWith(0.08); expect(mock.charge).wasCalledWith(-240); + */ + expect(mock.size).wasCalledWith([940, 640]); + expect(mock.linkDistance).wasCalledWith(240); + expect(mock.gravity).wasCalledWith(0.01); + expect(mock.charge).wasCalledWith(-1000); }); it('should be able to switch the distance', function() { diff --git a/html/admin/js/graphViewer/jasmine_test/specForceLayouter/forceLayouterUISpec.js b/html/admin/js/graphViewer/jasmine_test/specForceLayouter/forceLayouterUISpec.js index 8b7b22deeb..f846ce1998 100644 --- a/html/admin/js/graphViewer/jasmine_test/specForceLayouter/forceLayouterUISpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specForceLayouter/forceLayouterUISpec.js @@ -49,7 +49,7 @@ layouter = new ForceLayouter(config); list = document.createElement("ul"); document.body.appendChild(list); - list.id = "control_list"; + list.id = "control_layout_list"; layouterUI = new LayouterControls(list, layouter); spyOn(layouter, 'changeTo'); this.addMatchers({ @@ -90,15 +90,15 @@ runs(function() { layouterUI.addControlGravity(); - expect($("#control_list #control_gravity").length).toEqual(1); - expect($("#control_list #control_gravity")[0]).toConformToListCSS(); + expect($("#control_layout_list #control_layout_gravity").length).toEqual(1); + expect($("#control_layout_list #control_layout_gravity")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_gravity"); + helper.simulateMouseEvent("click", "control_layout_gravity"); - expect($("#control_gravity_modal").length).toEqual(1); + expect($("#control_layout_gravity_modal").length).toEqual(1); - $("#control_gravity_value").attr("value", 42); - helper.simulateMouseEvent("click", "control_gravity_submit"); + $("#control_layout_gravity_value").attr("value", 42); + helper.simulateMouseEvent("click", "control_layout_gravity_submit"); expect(layouter.changeTo).toHaveBeenCalledWith({ gravity: "42" @@ -107,7 +107,7 @@ }); waitsFor(function() { - return $("#control_gravity_modal").length === 0; + return $("#control_layout_gravity_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); @@ -115,15 +115,15 @@ runs(function() { layouterUI.addControlDistance(); - expect($("#control_list #control_distance").length).toEqual(1); - expect($("#control_list #control_distance")[0]).toConformToListCSS(); + expect($("#control_layout_list #control_layout_distance").length).toEqual(1); + expect($("#control_layout_list #control_layout_distance")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_distance"); + helper.simulateMouseEvent("click", "control_layout_distance"); - expect($("#control_distance_modal").length).toEqual(1); + expect($("#control_layout_distance_modal").length).toEqual(1); - $("#control_distance_value").attr("value", 42); - helper.simulateMouseEvent("click", "control_distance_submit"); + $("#control_layout_distance_value").attr("value", 42); + helper.simulateMouseEvent("click", "control_layout_distance_submit"); expect(layouter.changeTo).toHaveBeenCalledWith({ distance: "42" @@ -132,7 +132,7 @@ }); waitsFor(function() { - return $("#control_distance_modal").length === 0; + return $("#control_layout_distance_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); @@ -140,15 +140,15 @@ runs(function() { layouterUI.addControlCharge(); - expect($("#control_list #control_charge").length).toEqual(1); - expect($("#control_list #control_charge")[0]).toConformToListCSS(); + expect($("#control_layout_list #control_layout_charge").length).toEqual(1); + expect($("#control_layout_list #control_layout_charge")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_charge"); + helper.simulateMouseEvent("click", "control_layout_charge"); - expect($("#control_charge_modal").length).toEqual(1); + expect($("#control_layout_charge_modal").length).toEqual(1); - $("#control_charge_value").attr("value", 42); - helper.simulateMouseEvent("click", "control_charge_submit"); + $("#control_layout_charge_value").attr("value", 42); + helper.simulateMouseEvent("click", "control_layout_charge_submit"); expect(layouter.changeTo).toHaveBeenCalledWith({ charge: "42" @@ -157,16 +157,16 @@ }); waitsFor(function() { - return $("#control_charge_modal").length === 0; + return $("#control_layout_charge_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); it('should be able to add all controls to the list', function () { layouterUI.addAll(); - expect($("#control_list #control_gravity").length).toEqual(1); - expect($("#control_list #control_distance").length).toEqual(1); - expect($("#control_list #control_charge").length).toEqual(1); + expect($("#control_layout_list #control_layout_gravity").length).toEqual(1); + expect($("#control_layout_list #control_layout_distance").length).toEqual(1); + expect($("#control_layout_list #control_layout_charge").length).toEqual(1); }); }); diff --git a/html/admin/js/graphViewer/jasmine_test/specGraphViewer/graphViewerUISpec.js b/html/admin/js/graphViewer/jasmine_test/specGraphViewer/graphViewerUISpec.js index 08dab1b591..fbb3eae4d3 100644 --- a/html/admin/js/graphViewer/jasmine_test/specGraphViewer/graphViewerUISpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specGraphViewer/graphViewerUISpec.js @@ -57,6 +57,9 @@ value: value }; }; + r.getCollections = function(callback) { + callback(["nodes"], ["edges"]); + }; return r; }; //Mock for ZoomManager @@ -190,17 +193,41 @@ }); it('should contain the objects from eventDispatcher', function() { - expect($(toolboxSelector + " #control_drag").length).toEqual(1); - expect($(toolboxSelector + " #control_edit").length).toEqual(1); - expect($(toolboxSelector + " #control_expand").length).toEqual(1); - expect($(toolboxSelector + " #control_delete").length).toEqual(1); - expect($(toolboxSelector + " #control_connect").length).toEqual(1); + expect($(toolboxSelector + " #control_event_drag").length).toEqual(1); + expect($(toolboxSelector + " #control_event_edit").length).toEqual(1); + expect($(toolboxSelector + " #control_event_expand").length).toEqual(1); + expect($(toolboxSelector + " #control_event_delete").length).toEqual(1); + expect($(toolboxSelector + " #control_event_connect").length).toEqual(1); }); it('should have the correct layout', function() { expect($(toolboxSelector)[0]).toConformToToolboxLayout(); }); + it('should create the additional mouse-icon box', function() { + var pointerBox = $("#contentDiv #mousepointer"); + expect(pointerBox.length).toEqual(1); + expect(pointerBox[0]).toBeTag("div"); + expect(pointerBox[0]).toBeOfClass("mousepointer"); + }); + + it('should position the mouse-icon box next to the mouse pointer', function() { + var x = 40, + y = 50, + pointerBox = $("#contentDiv #mousepointer"); + + helper.simulateMouseMoveEvent("graphViewerSVG", x, y); + expect(pointerBox.offset().left).toEqual(x + 7); + expect(pointerBox.offset().top).toEqual(y + 12); + + x = 66; + y = 33; + + helper.simulateMouseMoveEvent("graphViewerSVG", x, y); + expect(pointerBox.offset().left).toEqual(x + 7); + expect(pointerBox.offset().top).toEqual(y + 12); + }); + }); describe('checking the menubar', function() { @@ -263,12 +290,14 @@ expect($(menuSelector + " #control_gradientcolour").length).toEqual(1); }); */ + /* it('should contain a menu for the adapter', function() { var menuSelector = "#contentDiv #menubar #adaptermenu"; expect($(menuSelector).length).toEqual(1); expect($(menuSelector)[0]).toBeADropdownMenu(); expect($(menuSelector + " #control_collections").length).toEqual(1); }); + */ /* it('should contain a menu for the layouter', function() { var menuSelector = "#contentDiv #menubar #layoutermenu"; @@ -280,6 +309,15 @@ }); */ + it('should contain a general configure menu', function() { + var menuSelector = "#contentDiv #menubar #configuremenu"; + expect($(menuSelector).length).toEqual(1); + expect($(menuSelector)[0]).toBeADropdownMenu(); + expect($("> button", menuSelector).text()).toEqual("Configure "); + expect($(menuSelector + " #control_adapter_collections").length).toEqual(1); + expect($(menuSelector + " #control_node_label").length).toEqual(1); + }); + it('should have the same layout as the web interface', function() { var header = div.children[0], transHeader = header.firstChild, @@ -296,8 +334,10 @@ expect(searchField.id).toEqual("transparentPlaceholder"); expect(searchField.className).toEqual("pull-left"); expect(searchField.children[0].id).toEqual("attribute"); - expect(searchField.children[1].id).toEqual("value"); - expect(searchField.children[2].id).toEqual("loadnode"); + expect(searchField.children[1]).toBeTag("span"); + expect(searchField.children[1].textContent).toEqual("=="); + expect(searchField.children[2].id).toEqual("value"); + expect(searchField.children[3].id).toEqual("loadnode"); }); }); @@ -444,7 +484,7 @@ runs (function() { $("#contentDiv #menubar #value").attr("value", "0"); helper.simulateMouseEvent("click", "loadnode"); - helper.simulateMouseEvent("click", "control_expand"); + helper.simulateMouseEvent("click", "control_event_expand"); }); waits(waittime); diff --git a/html/admin/js/graphViewer/jasmine_test/specNodeReducer/nodeReducerSpec.js b/html/admin/js/graphViewer/jasmine_test/specNodeReducer/nodeReducerSpec.js index 384b37d18e..6438df2efa 100644 --- a/html/admin/js/graphViewer/jasmine_test/specNodeReducer/nodeReducerSpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specNodeReducer/nodeReducerSpec.js @@ -98,6 +98,12 @@ expect(reducer.getCommunity.length).toEqual(2); }); + it('should offer a function for bucket sort of nodes', function() { + expect(reducer.bucketNodes).toBeDefined(); + expect(reducer.bucketNodes).toEqual(jasmine.any(Function)); + expect(reducer.bucketNodes.length).toEqual(2); + }); + }); describe('checking community identification', function() { @@ -154,6 +160,146 @@ }); + describe('checking bucket sort of nodes', function() { + var allNodes, buckets; + + beforeEach(function() { + allNodes = []; + + this.addMatchers({ + toContainAll: function(objs) { + var bucket = this.actual, + passed = true; + _.each(bucket, function(n) { + var i; + for (i = 0; i < objs.length; i++) { + if (objs[i] === n) { + return; + } + } + passed = false; + }); + this.message = function() { + return JSON.stringify(bucket) + + " should contain all of " + + JSON.stringify(objs); + }; + return passed; + } + }); + }); + + it('should not bucket anything if #nodes <= #buckets', function() { + buckets = 5; + allNodes.push({a: 1}); + allNodes.push({a: 1}); + allNodes.push({a: 1}); + allNodes.push({a: 1}); + allNodes.push({a: 1}); + var res = reducer.bucketNodes(allNodes, buckets); + expect(res.length).toEqual(5); + expect(res[0].length).toEqual(1); + expect(res[1].length).toEqual(1); + expect(res[2].length).toEqual(1); + expect(res[3].length).toEqual(1); + expect(res[4].length).toEqual(1); + }); + + it('should create at most the given amount of buckets', function() { + buckets = 3; + allNodes.push({a: 1}); + allNodes.push({b: 2}); + allNodes.push({c: 3}); + allNodes.push({d: 4}); + allNodes.push({e: 5}); + allNodes.push({f: 6}); + + var res = reducer.bucketNodes(allNodes, buckets); + expect(res.length).toEqual(3); + }); + + it('should uniformly distribute dissimilar nodes', function() { + buckets = 3; + allNodes.push({a: 1}); + allNodes.push({b: 2}); + allNodes.push({c: 3}); + allNodes.push({d: 4}); + allNodes.push({e: 5}); + allNodes.push({f: 6}); + allNodes.push({g: 7}); + allNodes.push({h: 8}); + allNodes.push({i: 9}); + + var res = reducer.bucketNodes(allNodes, buckets); + expect(res[0].length).toEqual(3); + expect(res[1].length).toEqual(3); + expect(res[2].length).toEqual(3); + }); + + it('should bucket clearly similar nodes together', function() { + buckets = 3; + var a1, a2 ,a3, + b1, b2, b3, + c1, c2, c3, + resArray, + res1, + res2, + res3; + + a1 = {a: 1}; + a2 = {a: 1}; + a3 = {a: 1}; + + b1 = {b: 2}; + b2 = {b: 2}; + b3 = {b: 2}; + + c1 = {c: 3}; + c2 = {c: 3}; + c3 = {c: 3}; + + allNodes.push(a1); + allNodes.push(a2); + allNodes.push(a3); + allNodes.push(b1); + allNodes.push(b2); + allNodes.push(b3); + allNodes.push(c1); + allNodes.push(c2); + allNodes.push(c3); + + resArray = reducer.bucketNodes(allNodes, buckets); + res1 = resArray[0]; + res2 = resArray[1]; + res3 = resArray[2]; + + if (res1[0].a !== undefined) { + expect(res1).toContainAll([a1, a2, a3]); + } else if (res2[0].a !== undefined) { + expect(res2).toContainAll([a1, a2, a3]); + } else { + expect(res3).toContainAll([a1, a2, a3]); + } + + if (res1[0].b !== undefined) { + expect(res1).toContainAll([b1, b2, b3]); + } else if (res2[0].b !== undefined) { + expect(res2).toContainAll([b1, b2, b3]); + } else { + expect(res3).toContainAll([b1, b2, b3]); + } + + if (res1[0].c !== undefined) { + expect(res1).toContainAll([c1, c2, c3]); + } else if (res2[0].c !== undefined) { + expect(res2).toContainAll([c1, c2, c3]); + } else { + expect(res3).toContainAll([c1, c2, c3]); + } + }); + + }); + }); }); }()); \ No newline at end of file diff --git a/html/admin/js/graphViewer/jasmine_test/specNodeShaper/nodeShaperUISpec.js b/html/admin/js/graphViewer/jasmine_test/specNodeShaper/nodeShaperUISpec.js index ddd1d29d42..1aa40a9600 100644 --- a/html/admin/js/graphViewer/jasmine_test/specNodeShaper/nodeShaperUISpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specNodeShaper/nodeShaperUISpec.js @@ -47,7 +47,7 @@ shaper = new NodeShaper(d3.select("svg")); list = document.createElement("ul"); document.body.appendChild(list); - list.id = "control_list"; + list.id = "control_node_list"; shaperUI = new NodeShaperControls(list, shaper); spyOn(shaper, 'changeTo'); this.addMatchers({ @@ -94,10 +94,10 @@ runs(function() { shaperUI.addControlOpticShapeNone(); - expect($("#control_list #control_none").length).toEqual(1); - expect($("#control_list #control_none")[0]).toConformToListCSS(); + expect($("#control_node_list #control_node_none").length).toEqual(1); + expect($("#control_node_list #control_node_none")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_none"); + helper.simulateMouseEvent("click", "control_node_none"); expect(shaper.changeTo).toHaveBeenCalledWith({ shape: { @@ -111,14 +111,14 @@ runs(function() { shaperUI.addControlOpticShapeCircle(); - expect($("#control_list #control_circle").length).toEqual(1); - expect($("#control_list #control_circle")[0]).toConformToListCSS(); + expect($("#control_node_list #control_node_circle").length).toEqual(1); + expect($("#control_node_list #control_node_circle")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_circle"); - expect($("#control_circle_modal").length).toEqual(1); + helper.simulateMouseEvent("click", "control_node_circle"); + expect($("#control_node_circle_modal").length).toEqual(1); - $("#control_circle_radius").attr("value", 42); - helper.simulateMouseEvent("click", "control_circle_submit"); + $("#control_node_circle_radius").attr("value", 42); + helper.simulateMouseEvent("click", "control_node_circle_submit"); expect(shaper.changeTo).toHaveBeenCalledWith({ shape: { @@ -129,7 +129,7 @@ }); waitsFor(function() { - return $("#control_circle_modal").length === 0; + return $("#control_node_circle_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); @@ -138,13 +138,13 @@ runs(function() { shaperUI.addControlOpticShapeRect(); - expect($("#control_list #control_rect").length).toEqual(1); - expect($("#control_list #control_rect")[0]).toConformToListCSS(); + expect($("#control_node_list #control_node_rect").length).toEqual(1); + expect($("#control_node_list #control_node_rect")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_rect"); - $("#control_rect_width").attr("value", 42); - $("#control_rect_height").attr("value", 12); - helper.simulateMouseEvent("click", "control_rect_submit"); + helper.simulateMouseEvent("click", "control_node_rect"); + $("#control_node_rect_width").attr("value", 42); + $("#control_node_rect_height").attr("value", 12); + helper.simulateMouseEvent("click", "control_node_rect_submit"); expect(shaper.changeTo).toHaveBeenCalledWith({ shape: { @@ -156,7 +156,7 @@ }); waitsFor(function() { - return $("#control_rect_modal").length === 0; + return $("#control_node_rect_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); @@ -165,12 +165,12 @@ runs(function() { shaperUI.addControlOpticLabel(); - expect($("#control_list #control_label").length).toEqual(1); - expect($("#control_list #control_label")[0]).toConformToListCSS(); + expect($("#control_node_list #control_node_label").length).toEqual(1); + expect($("#control_node_list #control_node_label")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_label"); - $("#control_label_key").attr("value", "theAnswer"); - helper.simulateMouseEvent("click", "control_label_submit"); + helper.simulateMouseEvent("click", "control_node_label"); + $("#control_node_label_key").attr("value", "theAnswer"); + helper.simulateMouseEvent("click", "control_node_label_submit"); expect(shaper.changeTo).toHaveBeenCalledWith({ label: "theAnswer" @@ -178,7 +178,7 @@ }); waitsFor(function() { - return $("#control_label_modal").length === 0; + return $("#control_node_label_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); @@ -187,13 +187,13 @@ runs(function() { shaperUI.addControlOpticSingleColour(); - expect($("#control_list #control_singlecolour").length).toEqual(1); - expect($("#control_list #control_singlecolour")[0]).toConformToListCSS(); + expect($("#control_node_list #control_node_singlecolour").length).toEqual(1); + expect($("#control_node_list #control_node_singlecolour")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_singlecolour"); - $("#control_singlecolour_fill").attr("value", "#123456"); - $("#control_singlecolour_stroke").attr("value", "#654321"); - helper.simulateMouseEvent("click", "control_singlecolour_submit"); + helper.simulateMouseEvent("click", "control_node_singlecolour"); + $("#control_node_singlecolour_fill").attr("value", "#123456"); + $("#control_node_singlecolour_stroke").attr("value", "#654321"); + helper.simulateMouseEvent("click", "control_node_singlecolour_submit"); expect(shaper.changeTo).toHaveBeenCalledWith({ color: { @@ -205,7 +205,7 @@ }); waitsFor(function() { - return $("#control_singlecolour_modal").length === 0; + return $("#control_node_singlecolour_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); @@ -214,12 +214,12 @@ runs(function() { shaperUI.addControlOpticAttributeColour(); - expect($("#control_list #control_attributecolour").length).toEqual(1); - expect($("#control_list #control_attributecolour")[0]).toConformToListCSS(); + expect($("#control_node_list #control_node_attributecolour").length).toEqual(1); + expect($("#control_node_list #control_node_attributecolour")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_attributecolour"); - $("#control_attributecolour_key").attr("value", "label"); - helper.simulateMouseEvent("click", "control_attributecolour_submit"); + helper.simulateMouseEvent("click", "control_node_attributecolour"); + $("#control_node_attributecolour_key").attr("value", "label"); + helper.simulateMouseEvent("click", "control_node_attributecolour_submit"); expect(shaper.changeTo).toHaveBeenCalledWith({ color: { @@ -230,7 +230,7 @@ }); waitsFor(function() { - return $("#control_attributecolour_modal").length === 0; + return $("#control_node_attributecolour_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); @@ -239,13 +239,13 @@ runs(function() { shaperUI.addControlOpticExpandColour(); - expect($("#control_list #control_expandcolour").length).toEqual(1); - expect($("#control_list #control_expandcolour")[0]).toConformToListCSS(); + expect($("#control_node_list #control_node_expandcolour").length).toEqual(1); + expect($("#control_node_list #control_node_expandcolour")[0]).toConformToListCSS(); - helper.simulateMouseEvent("click", "control_expandcolour"); - $("#control_expandcolour_expanded").attr("value", "#123456"); - $("#control_expandcolour_collapsed").attr("value", "#654321"); - helper.simulateMouseEvent("click", "control_expandcolour_submit"); + helper.simulateMouseEvent("click", "control_node_expandcolour"); + $("#control_node_expandcolour_expanded").attr("value", "#123456"); + $("#control_node_expandcolour_collapsed").attr("value", "#654321"); + helper.simulateMouseEvent("click", "control_node_expandcolour_submit"); expect(shaper.changeTo).toHaveBeenCalledWith({ color: { @@ -257,7 +257,7 @@ }); waitsFor(function() { - return $("#control_expandcolour_modal").length === 0; + return $("#control_node_expandcolour_modal").length === 0; }, 2000, "The modal dialog should disappear."); }); @@ -266,13 +266,13 @@ it('should be able to add all optic controls to the list', function () { shaperUI.addAllOptics(); - expect($("#control_list #control_none").length).toEqual(1); - expect($("#control_list #control_circle").length).toEqual(1); - expect($("#control_list #control_rect").length).toEqual(1); - expect($("#control_list #control_label").length).toEqual(1); - expect($("#control_list #control_singlecolour").length).toEqual(1); - expect($("#control_list #control_attributecolour").length).toEqual(1); - expect($("#control_list #control_expandcolour").length).toEqual(1); + expect($("#control_node_list #control_node_none").length).toEqual(1); + expect($("#control_node_list #control_node_circle").length).toEqual(1); + expect($("#control_node_list #control_node_rect").length).toEqual(1); + expect($("#control_node_list #control_node_label").length).toEqual(1); + expect($("#control_node_list #control_node_singlecolour").length).toEqual(1); + expect($("#control_node_list #control_node_attributecolour").length).toEqual(1); + expect($("#control_node_list #control_node_expandcolour").length).toEqual(1); }); it('should be able to add all action controls to the list', function () { @@ -283,13 +283,13 @@ it('should be able to add all controls to the list', function () { shaperUI.addAll(); - expect($("#control_list #control_none").length).toEqual(1); - expect($("#control_list #control_circle").length).toEqual(1); - expect($("#control_list #control_rect").length).toEqual(1); - expect($("#control_list #control_label").length).toEqual(1); - expect($("#control_list #control_singlecolour").length).toEqual(1); - expect($("#control_list #control_attributecolour").length).toEqual(1); - expect($("#control_list #control_expandcolour").length).toEqual(1); + expect($("#control_node_list #control_node_none").length).toEqual(1); + expect($("#control_node_list #control_node_circle").length).toEqual(1); + expect($("#control_node_list #control_node_rect").length).toEqual(1); + expect($("#control_node_list #control_node_label").length).toEqual(1); + expect($("#control_node_list #control_node_singlecolour").length).toEqual(1); + expect($("#control_node_list #control_node_attributecolour").length).toEqual(1); + expect($("#control_node_list #control_node_expandcolour").length).toEqual(1); }); }); diff --git a/html/admin/js/graphViewer/jasmine_test/specZoomManager/zoomManagerSpec.js b/html/admin/js/graphViewer/jasmine_test/specZoomManager/zoomManagerSpec.js index ce6e66df24..85ca0acd12 100644 --- a/html/admin/js/graphViewer/jasmine_test/specZoomManager/zoomManagerSpec.js +++ b/html/admin/js/graphViewer/jasmine_test/specZoomManager/zoomManagerSpec.js @@ -214,7 +214,7 @@ fontMax = 16; fontMin = 6; radMax = 25; - radMin = 1; + radMin = 4; distRBase = 100; minScale = radMin / radMax; toggleScale = fontMin / fontMax; diff --git a/html/admin/js/graphViewer/ui/arangoAdapterControls.js b/html/admin/js/graphViewer/ui/arangoAdapterControls.js index 8d16e1fafb..4be485c9bd 100644 --- a/html/admin/js/graphViewer/ui/arangoAdapterControls.js +++ b/html/admin/js/graphViewer/ui/arangoAdapterControls.js @@ -41,23 +41,32 @@ function ArangoAdapterControls(list, adapter) { baseClass = "adapter"; this.addControlChangeCollections = function() { - var prefix = "control_collections", - idprefix = prefix + "_"; - uiComponentsHelper.createButton(baseClass, list, "Collections", prefix, function() { - modalDialogHelper.createModalDialog("Switch Collections", - idprefix, [{ - type: "text", - id: "nodecollection" - },{ - type: "text", - id: "edgecollection" - }], function () { - var nodes = $("#" + idprefix + "nodecollection").attr("value"), - edges = $("#" + idprefix + "edgecollection").attr("value"); - adapter.changeTo(nodes, edges); - } - ); + var prefix = "control_adapter_collections", + idprefix = prefix + "_"; + adapter.getCollections(function(nodeCols, edgeCols) { + uiComponentsHelper.createButton(baseClass, list, "Collections", prefix, function() { + modalDialogHelper.createModalDialog("Switch Collections", + idprefix, [{ + type: "list", + id: "nodecollection", + objects: nodeCols + },{ + type: "list", + id: "edgecollection", + objects: edgeCols + },{ + type: "checkbox", + id: "undirected" + }], function () { + var nodes = $("#" + idprefix + "nodecollection").attr("value"), + edges = $("#" + idprefix + "edgecollection").attr("value"), + undirected = !!$("#" + idprefix + "undirected").attr("checked"); + adapter.changeTo(nodes, edges, undirected); + } + ); + }); }); + }; this.addAll = function() { diff --git a/html/admin/js/graphViewer/ui/edgeShaperControls.js b/html/admin/js/graphViewer/ui/edgeShaperControls.js index 185e7f5228..6df9be8843 100644 --- a/html/admin/js/graphViewer/ui/edgeShaperControls.js +++ b/html/admin/js/graphViewer/ui/edgeShaperControls.js @@ -41,7 +41,7 @@ function EdgeShaperControls(list, shaper) { baseClass = "graph"; this.addControlOpticShapeNone = function() { - var prefix = "control_none", + var prefix = "control_edge_none", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "None", prefix, function() { shaper.changeTo({ @@ -53,7 +53,7 @@ function EdgeShaperControls(list, shaper) { }; this.addControlOpticShapeArrow = function() { - var prefix = "control_arrow", + var prefix = "control_edge_arrow", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Arrow", prefix, function() { shaper.changeTo({ @@ -67,7 +67,7 @@ function EdgeShaperControls(list, shaper) { this.addControlOpticLabel = function() { - var prefix = "control_label", + var prefix = "control_edge_label", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Label", prefix, function() { modalDialogHelper.createModalDialog("Switch Label Attribute", @@ -88,7 +88,7 @@ function EdgeShaperControls(list, shaper) { this.addControlOpticSingleColour = function() { - var prefix = "control_singlecolour", + var prefix = "control_edge_singlecolour", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Single Colour", prefix, function() { modalDialogHelper.createModalDialog("Switch to Colour", @@ -109,7 +109,7 @@ function EdgeShaperControls(list, shaper) { }; this.addControlOpticAttributeColour = function() { - var prefix = "control_attributecolour", + var prefix = "control_edge_attributecolour", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Colour by Attribute", prefix, function() { modalDialogHelper.createModalDialog("Display colour by attribute", @@ -130,7 +130,7 @@ function EdgeShaperControls(list, shaper) { }; this.addControlOpticGradientColour = function() { - var prefix = "control_gradientcolour", + var prefix = "control_edge_gradientcolour", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Gradient Colour", prefix, function() { modalDialogHelper.createModalDialog("Change colours for gradient", diff --git a/html/admin/js/graphViewer/ui/eventDispatcherControls.js b/html/admin/js/graphViewer/ui/eventDispatcherControls.js index 3bd299abdb..a272432ed8 100644 --- a/html/admin/js/graphViewer/ui/eventDispatcherControls.js +++ b/html/admin/js/graphViewer/ui/eventDispatcherControls.js @@ -1,6 +1,6 @@ /*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */ /*global $, _, d3*/ -/*global document*/ +/*global document, window*/ /*global modalDialogHelper, uiComponentsHelper */ /*global EventDispatcher, EventLibrary*/ //////////////////////////////////////////////////////////////////////////////// @@ -29,12 +29,15 @@ /// @author Michael Hackstein /// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// -function EventDispatcherControls(list, nodeShaper, edgeShaper, dispatcherConfig) { +function EventDispatcherControls(list, cursorIconBox, nodeShaper, edgeShaper, dispatcherConfig) { "use strict"; if (list === undefined) { throw "A list element has to be given."; } + if (cursorIconBox === undefined) { + throw "The cursor decoration box has to be given."; + } if (nodeShaper === undefined) { throw "The NodeShaper has to be given."; } @@ -53,6 +56,10 @@ function EventDispatcherControls(list, nodeShaper, edgeShaper, dispatcherConfig) eventlib = new EventLibrary(), dispatcher = new EventDispatcher(nodeShaper, edgeShaper, dispatcherConfig), + setCursorIcon = function(icon) { + cursorIconBox.className = "mousepointer icon-" + icon; + }, + appendToList = function(button) { if (firstButton) { currentListGroup = document.createElement("div"); @@ -72,14 +79,14 @@ function EventDispatcherControls(list, nodeShaper, edgeShaper, dispatcherConfig) baseClass, list, title, - "control_" + title, + "control_event_" + title, callback ); }, createIcon = function(icon, title, callback) { var btn = uiComponentsHelper.createIconButton( icon, - "control_" + title, + "control_event_" + title, callback ); appendToList(btn); @@ -89,33 +96,72 @@ function EventDispatcherControls(list, nodeShaper, edgeShaper, dispatcherConfig) }, rebindEdges = function(actions) { dispatcher.rebind("edges", actions); + }, + rebindSVG = function(actions) { + dispatcher.rebind("svg", actions); + }, + + getCursorPosition = function (ev) { + var e = ev || window.event, + res = {}; + res.x = e.clientX; + res.y = e.clientY; + res.x += document.body.scrollLeft; + res.y += document.body.scrollTop; + return res; + }, + + getCursorPositionInSVG = function (ev) { + var pos = getCursorPosition(ev); + pos.x -= $('svg').offset().left; + pos.y -= $('svg').offset().top; + return pos; + }, + + moveCursorBox = function(ev) { + var pos = getCursorPosition(ev); + pos.x += 7; + pos.y += 12; + cursorIconBox.style.position = "absolute"; + cursorIconBox.style.left = pos.x + 'px'; + cursorIconBox.style.top = pos.y + 'px'; }; + dispatcher.fixSVG("mousemove", moveCursorBox); + dispatcher.fixSVG("mouseout", function() { + cursorIconBox.style.display = "none"; + }); + dispatcher.fixSVG("mouseover", function() { + cursorIconBox.style.display = "block"; + }); + this.addControlDrag = function() { - var prefix = "control_drag", + var prefix = "control_event_drag", idprefix = prefix + "_", + icon = "move", callback = function() { + setCursorIcon(icon); rebindNodes( { drag: dispatcher.events.DRAG }); rebindEdges(); - - + rebindSVG(); }; - createIcon("move", "drag", callback); + createIcon(icon, "drag", callback); }; this.addControlEdit = function() { - var prefix = "control_edit", + var prefix = "control_event_edit", idprefix = prefix + "_", + icon = "pencil", nodeCallback = function(n) { modalDialogHelper.createModalEditDialog( "Edit Node " + n._id, - "control_node_edit_", + "control_event_node_edit_", n._data, function(newData) { dispatcher.events.PATCHNODE(n, newData, function() { - $("#control_node_edit_modal").modal('hide'); + $("#control_event_node_edit_modal").modal('hide'); })(); } ); @@ -123,59 +169,83 @@ function EventDispatcherControls(list, nodeShaper, edgeShaper, dispatcherConfig) edgeCallback = function(e) { modalDialogHelper.createModalEditDialog( "Edit Edge " + e._data._from + "->" + e._data._to, - "control_edge_edit_", + "control_event_edge_edit_", e._data, function(newData) { dispatcher.events.PATCHEDGE(e, newData, function() { - $("#control_edge_edit_modal").modal('hide'); + $("#control_event_edge_edit_modal").modal('hide'); })(); } ); }, callback = function() { + setCursorIcon(icon); rebindNodes({click: nodeCallback}); rebindEdges({click: edgeCallback}); + rebindSVG(); }; - createIcon("pencil", "edit", callback); + createIcon(icon, "edit", callback); }; this.addControlExpand = function() { - var prefix = "control_expand", + var prefix = "control_event_expand", idprefix = prefix + "_", + icon = "plus", callback = function() { + setCursorIcon(icon); rebindNodes({click: dispatcher.events.EXPAND}); rebindEdges(); + rebindSVG(); }; - createIcon("plus", "expand", callback); + createIcon(icon, "expand", callback); }; this.addControlDelete = function() { - var prefix = "control_delete", + var prefix = "control_event_delete", idprefix = prefix + "_", + icon = "trash", callback = function() { + setCursorIcon(icon); rebindNodes({click: dispatcher.events.DELETENODE(function() { })}); rebindEdges({click: dispatcher.events.DELETEEDGE(function() { })}); + rebindSVG(); }; - createIcon("trash", "delete", callback); + createIcon(icon, "delete", callback); }; this.addControlConnect = function() { - var prefix = "control_connect", + var prefix = "control_event_connect", idprefix = prefix + "_", + icon = "resize-horizontal", callback = function() { - + setCursorIcon(icon); rebindNodes({ - mousedown: dispatcher.events.STARTCREATEEDGE(), + mousedown: dispatcher.events.STARTCREATEEDGE(function(startNode, ev) { + var pos = getCursorPositionInSVG(ev); + var moveCB = edgeShaper.addAnEdgeFollowingTheCursor(pos.x, pos.y); + dispatcher.bind("svg", "mousemove", function(ev) { + var pos = getCursorPositionInSVG(ev); + moveCB(pos.x, pos.y); + }); + }), mouseup: dispatcher.events.FINISHCREATEEDGE(function(edge){ + edgeShaper.removeCursorFollowingEdge(); + dispatcher.bind("svg", "mousemove", function(){}); }) }); rebindEdges(); + rebindSVG({ + mouseup: function() { + dispatcher.events.CANCELCREATEEDGE(); + edgeShaper.removeCursorFollowingEdge(); + } + }); }; - createIcon("resize-horizontal", "connect", callback); + createIcon(icon, "connect", callback); }; diff --git a/html/admin/js/graphViewer/ui/graphViewerUI.js b/html/admin/js/graphViewer/ui/graphViewerUI.js index b1329b7f73..18f5c37a1f 100644 --- a/html/admin/js/graphViewer/ui/graphViewerUI.js +++ b/html/admin/js/graphViewer/ui/graphViewerUI.js @@ -2,7 +2,7 @@ /*global document, $, _ */ /*global EventDispatcherControls, NodeShaperControls, EdgeShaperControls */ /*global LayouterControls, ArangoAdapterControls*/ -/*global GraphViewer, d3*/ +/*global GraphViewer, d3, window*/ //////////////////////////////////////////////////////////////////////////////// /// @brief Graph functionality /// @@ -30,7 +30,7 @@ /// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// -function GraphViewerUI(container, adapterConfig, optWidth, optHeight) { +function GraphViewerUI(container, adapterConfig, optWidth, optHeight, viewerConfig) { "use strict"; if (container === undefined) { @@ -48,7 +48,9 @@ function GraphViewerUI(container, adapterConfig, optWidth, optHeight) { height = optHeight || container.offsetHeight, menubar = document.createElement("ul"), background = document.createElement("div"), - svg, + mousePointerBox = document.createElement("div"), + svg, + makeBootstrapDropdown = function (div, id, title) { var btn, caret, list; div.className = "btn-group"; @@ -69,6 +71,7 @@ function GraphViewerUI(container, adapterConfig, optWidth, optHeight) { createSVG = function () { return d3.select("#" + container.id + " #background") .append("svg") + .attr("id", "graphViewerSVG") .attr("width",width) .attr("height",height) .attr("class", "pull-right graphViewer") @@ -78,13 +81,17 @@ function GraphViewerUI(container, adapterConfig, optWidth, optHeight) { var toolbox = document.createElement("div"), dispatcherUI = new EventDispatcherControls( toolbox, + mousePointerBox, graphViewer.nodeShaper, graphViewer.edgeShaper, graphViewer.dispatcherConfig ); toolbox.id = "toolbox"; toolbox.className = "btn-group btn-group-vertical pull-left toolbox"; + mousePointerBox.id = "mousepointer"; + mousePointerBox.className = "mousepointer"; background.appendChild(toolbox); + background.appendChild(mousePointerBox); dispatcherUI.addAll(); }, createMenu = function() { @@ -94,6 +101,14 @@ function GraphViewerUI(container, adapterConfig, optWidth, optHeight) { searchValueField = document.createElement("input"), searchStart = document.createElement("img"), buttons = document.createElement("div"), + equalsField = document.createElement("span"), + configureDropDown = document.createElement("div"), + configureList = makeBootstrapDropdown( + configureDropDown, + "configuredropdown", + "Configure" + ), + /* nodeShaperDropDown = document.createElement("div"), nodeShaperList = makeBootstrapDropdown( @@ -107,14 +122,12 @@ function GraphViewerUI(container, adapterConfig, optWidth, optHeight) { "edgeshaperdropdown", "Edges" ), - */ adapterDropDown = document.createElement("div"), adapterList = makeBootstrapDropdown( adapterDropDown, "adapterdropdown", "Connection" ), - /* layouterDropDown = document.createElement("div"), layouterList = makeBootstrapDropdown( layouterDropDown, @@ -133,11 +146,21 @@ function GraphViewerUI(container, adapterConfig, optWidth, optHeight) { layouterList, graphViewer.layouter ), - */ adapterUI = new ArangoAdapterControls( adapterList, graphViewer.adapter ), + */ + + nodeShaperUI = new NodeShaperControls( + configureList, + graphViewer.nodeShaper + ), + adapterUI = new ArangoAdapterControls( + configureList, + graphViewer.adapter + ), + searchFunction = function() { if (searchAttrField.value === "" || searchAttrField.value === undefined) { @@ -175,13 +198,18 @@ function GraphViewerUI(container, adapterConfig, optWidth, optHeight) { searchStart.height = 16; searchStart.src = "img/enter_icon.png"; + equalsField.className = "searchEqualsLabel"; + equalsField.appendChild(document.createTextNode("==")); + + /* nodeShaperDropDown.id = "nodeshapermenu"; edgeShaperDropDown.id = "edgeshapermenu"; layouterDropDown.id = "layoutermenu"; - */ - adapterDropDown.id = "adaptermenu"; + */ + configureDropDown.id = "configuremenu"; + searchStart.onclick = searchFunction; $(searchValueField).keypress(function(e) { @@ -195,34 +223,36 @@ function GraphViewerUI(container, adapterConfig, optWidth, optHeight) { menubar.appendChild(transparentHeader); transparentHeader.appendChild(searchDiv); searchDiv.appendChild(searchAttrField); + searchDiv.appendChild(equalsField); searchDiv.appendChild(searchValueField); searchDiv.appendChild(searchStart); transparentHeader.appendChild(buttons); + + buttons.appendChild(configureDropDown); + + adapterUI.addControlChangeCollections(); + nodeShaperUI.addControlOpticLabel(); + /* buttons.appendChild(nodeShaperDropDown); buttons.appendChild(edgeShaperDropDown); buttons.appendChild(layouterDropDown); - */ buttons.appendChild(adapterDropDown); - /* - transparentHeader.appendChild(nodeShaperDropDown); - transparentHeader.appendChild(edgeShaperDropDown); - transparentHeader.appendChild(adapterDropDown); - transparentHeader.appendChild(layouterDropDown); - */ - /* + nodeShaperUI.addAll(); edgeShaperUI.addAll(); layouterUI.addAll(); - */ adapterUI.addAll(); + */ }; container.appendChild(menubar); container.appendChild(background); background.className = "thumbnails"; background.id = "background"; svg = createSVG(); - graphViewer = new GraphViewer(svg, width, height, adapterConfig, {zoom: true}); + viewerConfig = viewerConfig || {}; + viewerConfig.zoom = true; + graphViewer = new GraphViewer(svg, width, height, adapterConfig, viewerConfig); createToolbox(); createMenu(); diff --git a/html/admin/js/graphViewer/ui/layouterControls.js b/html/admin/js/graphViewer/ui/layouterControls.js index cbd22cf4fa..88e4272af8 100644 --- a/html/admin/js/graphViewer/ui/layouterControls.js +++ b/html/admin/js/graphViewer/ui/layouterControls.js @@ -41,7 +41,7 @@ function LayouterControls(list, layouter) { baseClass = "layout"; this.addControlGravity = function() { - var prefix = "control_gravity", + var prefix = "control_layout_gravity", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Gravity", prefix, function() { modalDialogHelper.createModalDialog("Switch Gravity Strength", @@ -59,7 +59,7 @@ function LayouterControls(list, layouter) { }; this.addControlCharge = function() { - var prefix = "control_charge", + var prefix = "control_layout_charge", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Charge", prefix, function() { modalDialogHelper.createModalDialog("Switch Charge Strength", @@ -77,7 +77,7 @@ function LayouterControls(list, layouter) { }; this.addControlDistance = function() { - var prefix = "control_distance", + var prefix = "control_layout_distance", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Distance", prefix, function() { modalDialogHelper.createModalDialog("Switch Distance Strength", diff --git a/html/admin/js/graphViewer/ui/modalDialogHelper.js b/html/admin/js/graphViewer/ui/modalDialogHelper.js index 909698ed91..4989a2e351 100644 --- a/html/admin/js/graphViewer/ui/modalDialogHelper.js +++ b/html/admin/js/graphViewer/ui/modalDialogHelper.js @@ -57,7 +57,7 @@ var modalDialogHelper = modalDialogHelper || {}; document.body.removeChild(div); }; - headerDiv.className = "modal_header"; + headerDiv.className = "modal-header"; buttonDismiss.className = "close"; buttonDismiss.dataDismiss = "modal"; @@ -66,11 +66,11 @@ var modalDialogHelper = modalDialogHelper || {}; header.appendChild(document.createTextNode(title)); - bodyDiv.className = "modal_body"; + bodyDiv.className = "modal-body"; bodyTable.id = idprefix + "table"; - footerDiv.className = "modal_footer"; + footerDiv.className = "modal-footer"; buttonCancel.id = idprefix + "cancel"; buttonCancel.className = "btn btn-danger pull-left"; @@ -132,6 +132,25 @@ var modalDialogHelper = modalDialogHelper || {}; input.id = idprefix + o.id; contentTh.appendChild(input); break; + case "checkbox": + input = document.createElement("input"); + input.type = "checkbox"; + input.id = idprefix + o.id; + contentTh.appendChild(input); + break; + case "list": + input = document.createElement("select"); + input.id = idprefix + o.id; + contentTh.appendChild(input); + _.each(o.objects, function(entry) { + var option = document.createElement("option"); + option.value = entry; + option.appendChild( + document.createTextNode(entry) + ); + input.appendChild(option); + }); + break; default: //Sorry unknown table.removeChild(tr); diff --git a/html/admin/js/graphViewer/ui/nodeShaperControls.js b/html/admin/js/graphViewer/ui/nodeShaperControls.js index d0d942354c..75431aa981 100644 --- a/html/admin/js/graphViewer/ui/nodeShaperControls.js +++ b/html/admin/js/graphViewer/ui/nodeShaperControls.js @@ -41,7 +41,7 @@ function NodeShaperControls(list, shaper) { baseClass = "graph"; this.addControlOpticShapeNone = function() { - uiComponentsHelper.createButton(baseClass, list, "None", "control_none", function() { + uiComponentsHelper.createButton(baseClass, list, "None", "control_node_none", function() { shaper.changeTo({ shape: { type: NodeShaper.shapes.NONE @@ -51,7 +51,7 @@ function NodeShaperControls(list, shaper) { }; this.addControlOpticShapeCircle = function() { - var prefix = "control_circle", + var prefix = "control_node_circle", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Circle", prefix, function() { modalDialogHelper.createModalDialog("Switch to Circle", @@ -72,11 +72,11 @@ function NodeShaperControls(list, shaper) { }; this.addControlOpticShapeRect = function() { - var prefix = "control_rect", + var prefix = "control_node_rect", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Rectangle", prefix, function() { modalDialogHelper.createModalDialog("Switch to Rectangle", - "control_rect_", [{ + "control_node_rect_", [{ type: "text", id: "width" },{ @@ -98,7 +98,7 @@ function NodeShaperControls(list, shaper) { }; this.addControlOpticLabel = function() { - var prefix = "control_label", + var prefix = "control_node_label", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Label", prefix, function() { modalDialogHelper.createModalDialog("Switch Label Attribute", @@ -120,7 +120,7 @@ function NodeShaperControls(list, shaper) { ////////////////////////////////////////////////////////////////// this.addControlOpticSingleColour = function() { - var prefix = "control_singlecolour", + var prefix = "control_node_singlecolour", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Single Colour", prefix, function() { modalDialogHelper.createModalDialog("Switch to Colour", @@ -146,7 +146,7 @@ function NodeShaperControls(list, shaper) { }; this.addControlOpticAttributeColour = function() { - var prefix = "control_attributecolour", + var prefix = "control_node_attributecolour", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Colour by Attribute", prefix, function() { modalDialogHelper.createModalDialog("Display colour by attribute", @@ -167,7 +167,7 @@ function NodeShaperControls(list, shaper) { }; this.addControlOpticExpandColour = function() { - var prefix = "control_expandcolour", + var prefix = "control_node_expandcolour", idprefix = prefix + "_"; uiComponentsHelper.createButton(baseClass, list, "Expansion Colour", prefix, function() { modalDialogHelper.createModalDialog("Display colours for expansion", diff --git a/html/admin/js/models/arangoCollection.js b/html/admin/js/models/arangoCollection.js index 5db44790fb..8dfe067fc3 100644 --- a/html/admin/js/models/arangoCollection.js +++ b/html/admin/js/models/arangoCollection.js @@ -1,8 +1,9 @@ -/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ -/*global window, Backbone*/ +/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, white: true, plusplus: true, nonpropdel: true, continue: true, regexp: true */ +/*global require, window, Backbone */ window.arangoCollection = Backbone.Model.extend({ initialize: function () { + 'use strict'; }, urlRoot: "/_api/collection", diff --git a/html/admin/js/models/arangoDocument.js b/html/admin/js/models/arangoDocument.js index c7ff150028..1d77d01c24 100644 --- a/html/admin/js/models/arangoDocument.js +++ b/html/admin/js/models/arangoDocument.js @@ -1,8 +1,9 @@ -/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ -/*global window, Backbone*/ +/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, white: true, plusplus: true, nonpropdel: true, continue: true, regexp: true */ +/*global require, window, Backbone */ window.arangoDocument = Backbone.Model.extend({ initialize: function () { + 'use strict'; }, urlRoot: "/_api/document", defaults: { diff --git a/html/admin/js/models/arangoLog.js b/html/admin/js/models/arangoLog.js index 5b465af7fb..549780a5b7 100644 --- a/html/admin/js/models/arangoLog.js +++ b/html/admin/js/models/arangoLog.js @@ -1,8 +1,9 @@ -/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ -/*global window, Backbone*/ +/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, white: true, plusplus: true, nonpropdel: true, continue: true, regexp: true */ +/*global require, window, Backbone */ window.arangoLog = Backbone.Model.extend({ initialize: function () { + 'use strict'; }, urlRoot: "/_admin/log", defaults: { diff --git a/html/admin/js/models/arangoStatistics.js b/html/admin/js/models/arangoStatistics.js index 774f36913f..b639223092 100644 --- a/html/admin/js/models/arangoStatistics.js +++ b/html/admin/js/models/arangoStatistics.js @@ -1,10 +1,12 @@ -/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ -/*global window, Backbone*/ +/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, white: true, plusplus: true, nonpropdel: true, continue: true, regexp: true */ +/*global require, window, Backbone */ window.Statistics = Backbone.Model.extend({ defaults: { }, url: function() { + 'use strict'; + return "../statistics"; } }); diff --git a/html/admin/js/models/arangoStatisticsDescription.js b/html/admin/js/models/arangoStatisticsDescription.js index c5d4fecec1..7099ecbda7 100644 --- a/html/admin/js/models/arangoStatisticsDescription.js +++ b/html/admin/js/models/arangoStatisticsDescription.js @@ -1,5 +1,5 @@ -/*jslint indent: 2, nomen: true, maxlen: 100, sloppy: true, vars: true, white: true, plusplus: true */ -/*global window, Backbone*/ +/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, white: true, plusplus: true, nonpropdel: true, continue: true, regexp: true */ +/*global require, window, Backbone */ window.StatisticsDescription = Backbone.Model.extend({ defaults: { @@ -7,6 +7,8 @@ window.StatisticsDescription = Backbone.Model.extend({ "groups" : "" }, url: function() { + 'use strict'; + return "../statistics-description"; } }); diff --git a/html/admin/js/models/foxx.js b/html/admin/js/models/foxx.js index ddd9aaa7d9..4ab5af127b 100644 --- a/html/admin/js/models/foxx.js +++ b/html/admin/js/models/foxx.js @@ -1,3 +1,6 @@ +/*jslint indent: 2, nomen: true, maxlen: 120, vars: true, white: true, plusplus: true, nonpropdel: true, continue: true, regexp: true */ +/*global require, window, Backbone */ + window.Foxx = Backbone.Model.extend({ defaults: { "title": "", @@ -8,6 +11,8 @@ window.Foxx = Backbone.Model.extend({ }, url: function() { + 'use strict'; + if (this.get("_key")) { return "../aardvark/foxxes/" + this.get("_key"); } @@ -15,6 +20,8 @@ window.Foxx = Backbone.Model.extend({ }, isNew: function() { + 'use strict'; + return false; } diff --git a/html/admin/js/templates/documentsView.ejs b/html/admin/js/templates/documentsView.ejs index 6a913b81fe..1b4505ac34 100644 --- a/html/admin/js/templates/documentsView.ejs +++ b/html/admin/js/templates/documentsView.ejs @@ -35,7 +35,7 @@

Theres no way back…

diff --git a/html/admin/js/templates/graphView.ejs b/html/admin/js/templates/graphView.ejs index d6a3a77669..117433b683 100644 --- a/html/admin/js/templates/graphView.ejs +++ b/html/admin/js/templates/graphView.ejs @@ -1,22 +1,54 @@ -
-
- Please give the collections -
- -
- +
+ +
+ Configuration +
+ +
+ +
-
-
- -
- +
+ +
+ +
-
-
-
- + +
+ +
+ + +
+
+
+ +
+ +
+
+
+ +
+ +
+
+
+
+
-
-
-
\ No newline at end of file + + +
+
+ +
+
+ + + \ No newline at end of file diff --git a/html/admin/js/views/documentsView.js b/html/admin/js/views/documentsView.js index 202d6836a8..991e2a43f0 100644 --- a/html/admin/js/views/documentsView.js +++ b/html/admin/js/views/documentsView.js @@ -29,7 +29,16 @@ var documentsView = Backbone.View.extend({ "click #documents_next" : "nextDocuments", "click #confirmDeleteBtn" : "confirmDelete", "keyup .modal-body" : "listenKey", - "click .key" : "nop" + "click .key" : "nop", + "keyup" : "returnPressedHandler" + }, + + returnPressedHandler: function(event) { + if (event.keyCode === 13) { + if (!!$("#confirmDeleteBtn").attr("disabled") === false) { + this.confirmDelete(); + } + } }, nop: function(event) { @@ -128,11 +137,12 @@ var documentsView = Backbone.View.extend({ var thiselement = a.currentTarget.parentElement; this.idelement = $(thiselement).prev().prev(); this.alreadyClicked = true; - + $("#confirmDeleteBtn").attr("disabled", false); $('#docDeleteModal').modal('show'); }, confirmDelete: function () { + $("#confirmDeleteBtn").attr("disabled", true); this.reallyDelete(); }, reallyDelete: function () { @@ -152,7 +162,7 @@ var documentsView = Backbone.View.extend({ deleted = true; } else if (result === false) { - arangoHelper.arangoError('Document error'); + arangoHelper.arangoError('Could not delete document'); } } else if (this.type === 'edge') { diff --git a/html/admin/js/views/graphView.js b/html/admin/js/views/graphView.js index b8f3100cac..499a398f1f 100644 --- a/html/admin/js/views/graphView.js +++ b/html/admin/js/views/graphView.js @@ -14,20 +14,35 @@ window.graphView = Backbone.View.extend({ createViewer: function() { var ecol, - ncol, - aaconfig; + ncol, + aaconfig, + undirected, + label, + config; + + + ecol = $("#edgeCollection").val(); + ncol = $("#nodeCollection").val(); + undirected = !!$("#undirected").attr("checked"); + label = $("#nodeLabel").val(); - ecol = $("#edgeCollection")[0].value; - ncol = $("#nodeCollection")[0].value; - aaconfig = { type: "arango", nodeCollection: ncol, - edgeCollection: ecol + edgeCollection: ecol, + undirected: undirected }; + + if (label !== undefined && label !== "") { + config = { + nodeShaper: { + label: label + } + } + } - $("#creationDialog").remove(); - ui = new GraphViewerUI(document.getElementById("content"), aaconfig, 940, 680); + $("#background").remove(); + ui = new GraphViewerUI(document.getElementById("content"), aaconfig, 940, 680, config); }, diff --git a/js/Makefile.files b/js/Makefile.files index 0eebdd7108..854a16c1cc 100644 --- a/js/Makefile.files +++ b/js/Makefile.files @@ -48,6 +48,7 @@ JAVASCRIPT_JSLINT = \ `find @srcdir@/js/common/modules/org -name "*.js"` \ `find @srcdir@/js/client/modules -name "*.js"` \ `find @srcdir@/js/server/modules -name "*.js"` \ + `find @srcdir@/html/admin/js/models -name "*.js"` \ \ @srcdir@/js/client/client.js \ @srcdir@/js/server/server.js \ diff --git a/js/actions/api-aqlfunction.js b/js/actions/api-aqlfunction.js index f8860c1349..97802da138 100644 --- a/js/actions/api-aqlfunction.js +++ b/js/actions/api-aqlfunction.js @@ -241,7 +241,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); diff --git a/js/actions/api-collection.js b/js/actions/api-collection.js index 40957a6edd..383fcd7d2f 100644 --- a/js/actions/api-collection.js +++ b/js/actions/api-collection.js @@ -235,7 +235,7 @@ function post_api_collection (req, res) { actions.resultOk(req, res, actions.HTTP_OK, result, headers); } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } @@ -668,7 +668,7 @@ function put_api_collection_load (req, res, collection) { actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } @@ -720,7 +720,7 @@ function put_api_collection_unload (req, res, collection) { actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } @@ -750,7 +750,7 @@ function put_api_collection_truncate (req, res, collection) { actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } @@ -812,7 +812,7 @@ function put_api_collection_properties (req, res, collection) { actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } @@ -870,7 +870,7 @@ function put_api_collection_rename (req, res, collection) { actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } @@ -979,7 +979,7 @@ function delete_api_collection (req, res) { actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } } @@ -1012,7 +1012,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); diff --git a/js/actions/api-cursor.js b/js/actions/api-cursor.js index 87499a1a96..a1d370ceb4 100644 --- a/js/actions/api-cursor.js +++ b/js/actions/api-cursor.js @@ -137,7 +137,7 @@ var QUERY = internal.AQL_QUERY; /// /// Executes a query and extract the result in a single go: /// -/// @EXAMPLE_ARANGOSH_RUN{RestCreateCursorForLimitReturnSingle} +/// @EXAMPLE_ARANGOSH_RUN{RestCursorCreateCursorForLimitReturnSingle} /// var cn = "products"; /// db._drop(cn); /// db._create(cn); @@ -157,7 +157,7 @@ var QUERY = internal.AQL_QUERY; /// /// Executes a query and extract part of the result: /// -/// @EXAMPLE_ARANGOSH_RUN{RestCreateCursorForLimitReturn} +/// @EXAMPLE_ARANGOSH_RUN{RestCursorCreateCursorForLimitReturn} /// var cn = "products"; /// db._drop(cn); /// db._create(cn); @@ -182,7 +182,7 @@ var QUERY = internal.AQL_QUERY; /// /// Missing body: /// -/// @EXAMPLE_ARANGOSH_RUN{RestCreateCursorMissingBody} +/// @EXAMPLE_ARANGOSH_RUN{RestCursorCreateCursorMissingBody} /// var url = "/_api/cursor"; /// /// var response = logCurlRequest('POST', url, ''); @@ -194,7 +194,7 @@ var QUERY = internal.AQL_QUERY; /// /// Unknown collection: /// -/// @EXAMPLE_ARANGOSH_RUN{RestCreateCursorUnknownCollection} +/// @EXAMPLE_ARANGOSH_RUN{RestCursorCreateCursorUnknownCollection} /// var url = "/_api/cursor"; /// var body = '{ "query" : "FOR u IN unknowncoll LIMIT 2 RETURN u", "count" : true, "batchSize" : 2 }'; /// @@ -235,7 +235,7 @@ function POST_api_cursor(req, res) { // error occurred if (cursor instanceof Error) { - actions.resultException(req, res, cursor); + actions.resultException(req, res, cursor, undefined, false); return; } @@ -285,11 +285,27 @@ function POST_api_cursor(req, res) { /// /// Missing identifier /// -/// @verbinclude api-cursor-missing-cursor-identifier +/// @EXAMPLE_ARANGOSH_RUN{RestCursorMissingCursorIdentifier} +/// var url = "/_api/cursor"; +/// +/// var response = logCurlRequest('PUT', url, ''); +/// +/// assert(response.code === 400); +/// +/// logJsonResponse(response); +/// @END_EXAMPLE_ARANGOSH_RUN /// /// Unknown identifier /// -/// @verbinclude api-cursor-invalid-cursor-identifier +/// @EXAMPLE_ARANGOSH_RUN{RestCursorInvalidCursorIdentifier} +/// var url = "/_api/cursor/123123"; +/// +/// var response = logCurlRequest('PUT', url, ''); +/// +/// assert(response.code === 400); +/// +/// logJsonResponse(response); +/// @END_EXAMPLE_ARANGOSH_RUN //////////////////////////////////////////////////////////////////////////////// function PUT_api_cursor (req, res) { @@ -404,7 +420,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); diff --git a/js/actions/api-edges.js b/js/actions/api-edges.js index aa2ebc6520..c8ca64b66d 100644 --- a/js/actions/api-edges.js +++ b/js/actions/api-edges.js @@ -131,7 +131,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); diff --git a/js/actions/api-explain.js b/js/actions/api-explain.js index d0cd1bf156..9dcc7bb527 100644 --- a/js/actions/api-explain.js +++ b/js/actions/api-explain.js @@ -137,7 +137,7 @@ function POST_api_explain (req, res) { var result = EXPLAIN(json.query, json.bindVars); if (result instanceof Error) { - actions.resultException(req, res, result); + actions.resultException(req, res, result, undefined, false); return; } @@ -169,7 +169,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); diff --git a/js/actions/api-graph.js b/js/actions/api-graph.js index 4adc6d1738..7adc5b75eb 100644 --- a/js/actions/api-graph.js +++ b/js/actions/api-graph.js @@ -1770,7 +1770,7 @@ function post_graph_vertex_edges (req, res, g) { // error occurred if (cursor instanceof Error) { - actions.resultException(req, res, cursor); + actions.resultException(req, res, cursor, undefined, false); return; } @@ -2039,7 +2039,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); diff --git a/js/actions/api-index.js b/js/actions/api-index.js index 1881d1eaea..fbb2300691 100644 --- a/js/actions/api-index.js +++ b/js/actions/api-index.js @@ -890,7 +890,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); diff --git a/js/actions/api-query.js b/js/actions/api-query.js index 74dd046bbe..a09cbe4a48 100644 --- a/js/actions/api-query.js +++ b/js/actions/api-query.js @@ -126,7 +126,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); diff --git a/js/actions/api-simple.js b/js/actions/api-simple.js index e575b82ebd..bcea15cd64 100644 --- a/js/actions/api-simple.js +++ b/js/actions/api-simple.js @@ -115,7 +115,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); @@ -193,7 +193,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); @@ -312,7 +312,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); @@ -430,7 +430,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); @@ -522,7 +522,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); @@ -611,7 +611,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); @@ -688,7 +688,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); @@ -734,7 +734,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); @@ -828,7 +828,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); @@ -904,7 +904,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); @@ -990,7 +990,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); @@ -1083,7 +1083,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); diff --git a/js/actions/api-structure.js b/js/actions/api-structure.js index 8a2140b204..611dd4d64c 100644 --- a/js/actions/api-structure.js +++ b/js/actions/api-structure.js @@ -141,7 +141,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); diff --git a/js/actions/api-system.js b/js/actions/api-system.js index 711a9e42c5..f448ff9950 100644 --- a/js/actions/api-system.js +++ b/js/actions/api-system.js @@ -335,7 +335,7 @@ actions.defineHttp({ /// /// @EXAMPLES /// -/// @EXAMPLE_ARANGOSH_RUN{AdminStatistics1} +/// @EXAMPLE_ARANGOSH_RUN{RestAdminStatistics1} /// var url = "/_admin/statistics"; /// var response = logCurlRequest('GET', url); /// @@ -362,7 +362,7 @@ actions.defineHttp({ actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); @@ -402,7 +402,7 @@ actions.defineHttp({ /// /// @EXAMPLES /// -/// @EXAMPLE_ARANGOSH_RUN{AdminStatisticsDescription1} +/// @EXAMPLE_ARANGOSH_RUN{RestAdminStatisticsDescription1} /// var url = "/_admin/statistics-description"; /// var response = logCurlRequest('GET', url); /// @@ -611,7 +611,7 @@ actions.defineHttp({ actions.resultOk(req, res, actions.HTTP_OK, result); } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); diff --git a/js/actions/api-user.js b/js/actions/api-user.js index f3c95309df..7a17399f1d 100644 --- a/js/actions/api-user.js +++ b/js/actions/api-user.js @@ -456,7 +456,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); diff --git a/js/actions/key-value.js b/js/actions/key-value.js index d0e157af46..23cefe4d75 100644 --- a/js/actions/key-value.js +++ b/js/actions/key-value.js @@ -341,7 +341,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); @@ -428,7 +428,7 @@ actions.defineHttp({ } } catch (err) { - actions.resultException(req, res, err); + actions.resultException(req, res, err, undefined, false); } } }); diff --git a/js/server/modules/org/arangodb/actions.js b/js/server/modules/org/arangodb/actions.js index c08e27eede..b9ff4b53f6 100644 --- a/js/server/modules/org/arangodb/actions.js +++ b/js/server/modules/org/arangodb/actions.js @@ -1198,7 +1198,7 @@ function resultError (req, res, httpReturnCode, errorNum, errorMessage, headers, res.body = JSON.stringify(result); - if (headers !== undefined) { + if (headers !== undefined && headers !== null) { res.headers = headers; } } @@ -1806,36 +1806,42 @@ function indexNotFound (req, res, collection, index, headers) { //////////////////////////////////////////////////////////////////////////////// /// @brief generates an error for an exception /// -/// @FUN{actions.resultException(@FA{req}, @FA{res}, @FA{err}, @FA{headers})} +/// @FUN{actions.resultException(@FA{req}, @FA{res}, @FA{err}, @FA{headers}, @FA{verbose})} /// -/// The function generates an error response. +/// The function generates an error response. If @FA{verbose} is set to +/// @LIT{true} or not specified (the default), then the error stack trace will +/// be included in the error message if available. //////////////////////////////////////////////////////////////////////////////// -function resultException (req, res, err, headers) { +function resultException (req, res, err, headers, verbose) { 'use strict'; var code; var msg; var num; + if (verbose || verbose === undefined) { + msg = String(err.stack || err); + } + else { + msg = String(err); + } + if (err instanceof internal.ArangoError) { num = err.errorNum; - msg = err.errorMessage; code = exports.HTTP_BAD; if (num === 0) { num = arangodb.ERROR_INTERNAL; } - if (msg === "") { - msg = String(err.stack || err); + if (err.errorMessage !== "" && ! verbose) { + msg = err.errorMessage; } - else { - msg += ": " + String(err.stack || err); - } - + switch (num) { case arangodb.ERROR_INTERNAL: + case arangodb.ERROR_OUT_OF_MEMORY: code = exports.HTTP_SERVER_ERROR; break; @@ -1844,23 +1850,17 @@ function resultException (req, res, err, headers) { code = exports.HTTP_CONFLICT; break; } - - resultError(req, res, code, num, msg, headers); } else if (err instanceof TypeError) { num = arangodb.ERROR_TYPE_ERROR; code = exports.HTTP_BAD; - msg = String(err.stack || err); - - resultError(req, res, code, num, msg, headers); } - else { - resultError(req, res, - exports.HTTP_SERVER_ERROR, arangodb.ERROR_HTTP_SERVER_ERROR, - String(err.stack || err), - headers); + num = arangodb.ERROR_HTTP_SERVER_ERROR; + code = exports.HTTP_SERVER_ERROR; } + + resultError(req, res, code, num, msg, headers); } //////////////////////////////////////////////////////////////////////////////// diff --git a/js/server/modules/org/arangodb/foxx-manager.js b/js/server/modules/org/arangodb/foxx-manager.js index cb4e233966..3a37cd27f9 100644 --- a/js/server/modules/org/arangodb/foxx-manager.js +++ b/js/server/modules/org/arangodb/foxx-manager.js @@ -761,6 +761,11 @@ exports.developmentRoutes = function () { var prefix = doc.collectionPrefix; var app = module.createApp(appId); + + if (app === null) { + throw new Error("cannot find application '" + appId + "'"); + } + var r = routingAalApp(app, mount, prefix); routes.push(r); diff --git a/js/server/tests/ahuacatl-queries-variables.js b/js/server/tests/ahuacatl-queries-variables.js index 6629967e96..32b0983063 100644 --- a/js/server/tests/ahuacatl-queries-variables.js +++ b/js/server/tests/ahuacatl-queries-variables.js @@ -343,6 +343,63 @@ function ahuacatlQueryVariablesTestSuite () { var actual = getQueryResults(query); assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief collect and return (should omit any temporary variables) +//////////////////////////////////////////////////////////////////////////////// + + testTemporaryVariables1 : function () { + var data = [ + { name: "baz" }, + { name: "bar" } + ]; + var expected = [ + { "criteria" : "bar", "g" : [ { "y" : { "test" : "test", "name" : "bar" } } ] }, + { "criteria" : "baz", "g" : [ { "y" : { "test" : "test", "name" : "baz" } } ] } + ]; + + var query = "FOR y IN (FOR x IN " + JSON.stringify(data) + " LET object = (FOR a IN [ '1', '2' ] RETURN a) RETURN { test: \"test\", name: x.name }) COLLECT criteria = y.name INTO g LIMIT 10 RETURN { criteria: criteria, g: g }"; + + var actual = getQueryResults("LET result = (" + query + ") LIMIT 10 RETURN result"); + assertEqual([ expected ], actual); + + actual = getQueryResults(query); + assertEqual(expected, actual); + + // omit creating sub-objects + query = "FOR y IN (FOR x IN " + JSON.stringify(data) + " RETURN { test: \"test\", name: x.name }) COLLECT criteria = y.name INTO g LIMIT 10 RETURN { criteria: criteria, g: g }"; + + actual = getQueryResults("LET result = (" + query + ") LIMIT 10 RETURN result"); + assertEqual([ expected ], actual); + + actual = getQueryResults(query); + assertEqual(expected, actual); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief collect and return (should omit any temporary variables) +//////////////////////////////////////////////////////////////////////////////// + + testTemporaryVariables2 : function () { + var data = [ + { name: "baz", _id: "id1" }, + { name: "bar", _id: "id2" }, + { name: "foo", _id: "id3" } + ]; + var expected = [ + { "criteria" : "yid3", "g" : [ { "y" : { "x" : { "name" : "foo", "_id" : "id3" } } } ] }, + { "criteria" : "yid2", "g" : [ { "y" : { "x" : { "name" : "bar", "_id" : "id2" } } } ] }, + { "criteria" : "yid1", "g" : [ { "y" : { "x" : { "name" : "baz", "_id" : "id1" } } } ] } + ]; + + var query = "FOR y IN (FOR x IN " + JSON.stringify(data) + " RETURN { x: x }) COLLECT criteria = CONCAT(\"y\", y.x._id) INTO g SORT MAX(g[*].y.x._id) DESC LIMIT 10 RETURN { criteria: criteria, g: g }"; + + var actual = getQueryResults("LET result = (" + query + ") LIMIT 10 RETURN result"); + assertEqual([ expected ], actual); + + actual = getQueryResults(query); + assertEqual(expected, actual); } }; diff --git a/lib/Zip/ioapi.h b/lib/Zip/ioapi.h index 014737b6f0..35cd020f88 100644 --- a/lib/Zip/ioapi.h +++ b/lib/Zip/ioapi.h @@ -24,7 +24,13 @@ #ifdef _Z_OF #undef OF #define OF _Z_OF +#else +#ifndef OF +#define _Z_OF(args) args +#define OF _Z_OF #endif +#endif + #if (!defined(_WIN32)) && (!defined(WIN32)) && (!defined(__APPLE__))