From 82267aefae972054a2664f0045d26e1986b70ffc Mon Sep 17 00:00:00 2001
From: Piotr Gawron <piotr.gawron@uni.lu>
Date: Tue, 10 Jan 2017 18:41:27 +0100
Subject: [PATCH] search by query added

---
 frontend-js/src/main/js/ServerConnector.js    | 38 ++++++++++
 frontend-js/src/main/js/gui/SearchPanel.js    | 60 +++++++++++++++
 frontend-js/src/main/js/map/CustomMap.js      | 22 ++++++
 frontend-js/src/main/js/map/data/MapModel.js  | 52 +++++++++++++
 .../main/js/map/overlay/OverlayCollection.js  | 18 +++++
 .../main/js/map/overlay/SearchDbOverlay.js    | 74 ++++++++++++++++++-
 .../src/test/js/gui/SearchPanel-test.js       | 19 ++++-
 frontend-js/src/test/js/helper.js             | 13 ++++
 .../js/map/overlay/OverlayCollection-test.js  | 51 +++++++++++++
 ...9177&projectId=sample&token=MOCK_TOKEN_ID& |  1 +
 ...ectId=sample&query=s1&token=MOCK_TOKEN_ID& |  1 +
 11 files changed, 346 insertions(+), 3 deletions(-)
 create mode 100644 frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329158,329159,329169,329173,329175,329177&projectId=sample&token=MOCK_TOKEN_ID&
 create mode 100644 frontend-js/testFiles/apiCalls/project/getElementsByQuery/perfectMatch=false&projectId=sample&query=s1&token=MOCK_TOKEN_ID&

diff --git a/frontend-js/src/main/js/ServerConnector.js b/frontend-js/src/main/js/ServerConnector.js
index a3c34b867e..0258876860 100644
--- a/frontend-js/src/main/js/ServerConnector.js
+++ b/frontend-js/src/main/js/ServerConnector.js
@@ -736,6 +736,22 @@ ServerConnector.getClosestElementsByCoordinatesUrl = function(params) {
   });
 };
 
+ServerConnector.getElementsByQueryUrl = function(params) {
+  var query = params.query;
+  var projectId = params.projectId;
+  var token = params.token;
+  var perfectMatch =params.perfectMatch;
+
+  return this.getApiUrl({type:"project",
+    method:"getElementsByQuery",
+    params: {
+      projectId: projectId, 
+      query: query, 
+      perfectMatch: perfectMatch,
+      token: token},
+  });
+};
+
 ServerConnector.getConfigurationParam = function(paramId) {
   var self = this;
   return new Promise(function(resolve, reject) {
@@ -979,6 +995,28 @@ ServerConnector.getClosestElementsByCoordinates = function(params) {
   });
 };
 
+ServerConnector.getElementsByQuery = function(params) {
+  var self = this;
+  return new Promise(function(resolve, reject) {
+    return self.getProjectId(params.projectId).then(function(result) {
+      params.projectId = result;
+      return self.getToken();
+    }).then(function(token) {
+      params.token = token;
+      return self.readFile(self.getElementsByQueryUrl(params));
+    }).then(function(content) {
+      var array=JSON.parse(content);
+      var result = [];
+      for (var i = 0; i < array.length; i++) {
+        result.push(new IdentifiedElement(array[i]));
+      }
+      resolve(result);
+    }).catch(function(exception){
+      reject(exception);
+    });
+  });
+};
+
 ServerConnector.addComment = function(params) {
   var self = this;
   return new Promise(function(resolve, reject) {
diff --git a/frontend-js/src/main/js/gui/SearchPanel.js b/frontend-js/src/main/js/gui/SearchPanel.js
index 5e9fb51b15..1c2c6c0172 100644
--- a/frontend-js/src/main/js/gui/SearchPanel.js
+++ b/frontend-js/src/main/js/gui/SearchPanel.js
@@ -15,6 +15,20 @@ function SearchPanel(params) {
   this.setMap(params.customMap);
 
   var searchDb = self.getMap().getOverlayByName('search');
+
+  var searchByQuery = function(){
+    var query = self.getSearchInput().value;
+    var perfect = self.getSearchPerfectMatch().checked;
+    searchDb.searchByQuery(query, perfect);
+  };
+  
+  self.getSearchButton().onclick= searchByQuery;
+  self.getSearchInput().onkeypress = function(event){
+    if (event.keyCode === 13) {
+      searchByQuery();  
+    }
+  };
+  
   if (searchDb === undefined) {
     throw new Error("Cannot find search db overlay");
   }
@@ -45,6 +59,15 @@ SearchPanel.prototype.setElement = function(element) {
   if (this.getContentElement() === undefined) {
     throw new Error("No tab-content div found in the search panel element");
   }
+  if (this.getSearchButton() === undefined) {
+    throw new Error("No searchButton found in the search panel element");
+  }
+  if (this.getSearchInput() === undefined) {
+    throw new Error("No searchInput found in the search panel element");
+  }
+  if (this.getSearchPerfectMatch() === undefined) {
+    throw new Error("No searchPerfectMatch found in the search panel element");
+  }
 };
 
 SearchPanel.prototype.getSearchQueryElement = function() {
@@ -81,6 +104,35 @@ SearchPanel.prototype.getNavElement = function() {
   }
 };
 
+function getElementByName(element, name) {
+  if (element!==undefined) {
+    if (element.getAttribute("name") === name) {
+      return element;
+    }
+    var children = element.children;
+    for (var i=0;i<children.length;i++) {
+      var child = children[i];
+      var res = getElementByName(child, name);
+      if (res!==undefined) {
+        return res;
+      }
+    }
+  }
+  return undefined;
+};
+
+SearchPanel.prototype.getSearchButton = function() {
+  return getElementByName(this.getSearchQueryElement(),"searchButton");
+};
+
+SearchPanel.prototype.getSearchInput = function() {
+  return getElementByName(this.getSearchQueryElement(),"searchInput");
+};
+
+SearchPanel.prototype.getSearchPerfectMatch = function() {
+  return getElementByName(this.getSearchQueryElement(),"searchPerfectMatch");
+};
+
 SearchPanel.prototype.getContentElement = function() {
   var searchResultsElement = this.getSearchResultsElement();
 
@@ -129,9 +181,12 @@ SearchPanel.prototype.refreshSearchResults = function() {
       resolve();
     }).catch(reject);
   });
+
 };
 
 SearchPanel.prototype.addResultTab = function(query, elements) {
+  var name = JSON.parse(query).query;
+  
   var tabId = "searchTab_" + this._tabIdCount;
   this._tabIdCount++;
 
@@ -146,8 +201,13 @@ SearchPanel.prototype.addResultTab = function(query, elements) {
 
   var navLi = document.createElement("li");
   navLi.className = navClass;
+  
   var navLink = document.createElement("a");
   navLink.href = "#" + tabId;
+  navLink.innerHTML = name;
+  navLink.onclick = function(){
+    $(this).tab('show');
+  }
   navLi.appendChild(navLink);
   if (query.name !== undefined) {
     navLink.innerHTML = query.name;
diff --git a/frontend-js/src/main/js/map/CustomMap.js b/frontend-js/src/main/js/map/CustomMap.js
index 689281d9de..0506155028 100644
--- a/frontend-js/src/main/js/map/CustomMap.js
+++ b/frontend-js/src/main/js/map/CustomMap.js
@@ -1789,4 +1789,26 @@ CustomMap.prototype.openCommentDialog = function() {
   });
 };
 
+CustomMap.prototype.fetchIdentifiedElements = function(elements, complete) {
+  var modelIds = [];
+  var modelElements = [];
+  var i;
+  for (i=0;i<elements.length;i++) {
+    var element = elements[i];
+    if (modelElements[element.getModelId()]===undefined) {
+      modelIds.push(element.getModelId());
+      modelElements[element.getModelId()]=[];
+    }
+    modelElements[element.getModelId()].push(element);
+  } 
+  
+  var promises = [];
+  for (i=0;i<modelIds.length;i++) {
+    var modelId = modelIds[i];
+    promises.push(this.getSubmodelById(modelId).getModel().getByIdentifiedElements(modelElements[modelId], complete));
+  }
+  return Promise.all(promises);
+  
+};
+
 module.exports = CustomMap;
diff --git a/frontend-js/src/main/js/map/data/MapModel.js b/frontend-js/src/main/js/map/data/MapModel.js
index 5e1b186239..677febb63a 100644
--- a/frontend-js/src/main/js/map/data/MapModel.js
+++ b/frontend-js/src/main/js/map/data/MapModel.js
@@ -133,6 +133,7 @@ MapModel.prototype.getAliasById = function(id, complete) {
   });
 };
 
+
 MapModel.prototype.getCompleteAliasById = function(id) {
   var self = this;
   return new Promise(function(resolve, reject) {
@@ -611,4 +612,55 @@ MapModel.prototype.getByIdentifiedElement = function(ie, complete) {
   }
 };
 
+MapModel.prototype.getByIdentifiedElements = function(identifiedElements, complete) {
+  var self = this;
+  var missingAliases = [];
+  var missingReactions = [];
+  
+  for (var i=0;i<identifiedElements.length;i++) {
+    var ie = identifiedElements[i];
+    if (!this.isAvailable(ie, complete)) {
+      if (ie.getType()==="ALIAS") {
+        missingAliases.push(ie.getId());
+      } else if (ie.getType()==="REACTION") {
+        missingReactions.push(ie.getId());
+      } else {
+        throw new Error("Unknown type "+ie);
+      }
+    }
+  }
+  
+  return new Promise(function(resolve,reject){
+    return self.getMissingElements({aliasIds:missingAliases, reactionIds:missingReactions, complete: true}).then(function(){
+      var promises =[];
+      for (var i=0;i<identifiedElements.length;i++) {
+        promises.push(self.getByIdentifiedElement(identifiedElements[i]));
+      }
+      return Promise.all(promises);
+    }).then(function(elements){
+      resolve(elements);
+    }).catch(reject);
+  });
+  
+};
+
+
+MapModel.prototype.isAvailable = function(ie, complete) {
+  var element;
+  if (ie.getType()==="ALIAS") {
+    element = this._aliases[ie.getId()];
+  } else if (ie.getType()==="REACTION") {
+    element = this._reactions[ie.getId()];
+  } else {
+    throw new Error("Unknown type: "+ie.getType(), complete);
+  }
+  if (element===undefined) {
+    return false;
+  } else if (complete) {
+    return element.isComplete();
+  } else {
+    return true;
+  }
+}
+
 module.exports = MapModel;
diff --git a/frontend-js/src/main/js/map/overlay/OverlayCollection.js b/frontend-js/src/main/js/map/overlay/OverlayCollection.js
index 91961f1123..bbf070e82e 100644
--- a/frontend-js/src/main/js/map/overlay/OverlayCollection.js
+++ b/frontend-js/src/main/js/map/overlay/OverlayCollection.js
@@ -273,4 +273,22 @@ OverlayCollection.prototype.getIcon = function(colorId, id) {
   return "marker/" + this._iconType + "/" + this._iconType + "_" + color + "_" + id + ".png";
 };
 
+OverlayCollection.prototype.splitQuery = function (query, useFullName) {
+  var result = [];
+  if (query.indexOf(";")>=0) {
+    result = query.split(";");
+  } else {
+    result = query.split(",");
+  }
+
+  for (var i=0;i<result.length;i++) {
+    result[i]=result[i].trim();
+  }
+  if (result.length>1 && useFullName) {
+    result.push(query);
+  }
+  return result;
+};
+
+
 module.exports = OverlayCollection;
diff --git a/frontend-js/src/main/js/map/overlay/SearchDbOverlay.js b/frontend-js/src/main/js/map/overlay/SearchDbOverlay.js
index 7d4fb9186c..d21ee6d771 100644
--- a/frontend-js/src/main/js/map/overlay/SearchDbOverlay.js
+++ b/frontend-js/src/main/js/map/overlay/SearchDbOverlay.js
@@ -27,14 +27,32 @@ SearchDbOverlay.QueryType = {
     SEARCH_BY_QUERY : "SEARCH_BY_QUERY",
 };
 
-function encodeQuery(type, model, arg){
+function encodeQuery(type, arg0, arg1){
   if (type === SearchDbOverlay.QueryType.SEARCH_BY_COORDINATES) {
-    return type + "_"+model.getId()+"_"+arg.x+","+arg.y;
+    var model = arg0;
+    var coordinates = arg1;
+    return JSON.stringify({
+      type:type,
+      modelId:model.getId(),
+      coordinates: coordinates
+    })
+  } else if (type === SearchDbOverlay.QueryType.SEARCH_BY_QUERY) {
+    var query = arg0;
+    var perfect = arg1;
+    return JSON.stringify({
+      type:type,
+      query:query,
+      perfect: perfect
+    })
   } else {
     throw new Error("Unknown query type: "+type);
   }
 }
 
+function decodeQuery(query){
+  return JSON.parse(query);
+}
+
 SearchDbOverlay.prototype.getElementsByQuery = function(query) {
   var self = this;
   return new Promise(function(resolve, reject){
@@ -109,6 +127,58 @@ SearchDbOverlay.prototype.searchByCoordinates = function(model, coordinates) {
   });
 };
 
+SearchDbOverlay.prototype.searchBySingleQuery = function(originalQuery, perfect) {
+  var self = this;
+  return new Promise(function(resolve, reject) {
+    var query = encodeQuery(SearchDbOverlay.QueryType.SEARCH_BY_QUERY, originalQuery, perfect);
+    if (self._elementsByQuery[query] !== undefined) {
+      resolve(self._elementsByQuery[query]);
+    } else {
+      return ServerConnector.getElementsByQuery({
+        query:originalQuery,
+        perfectMatch: perfect
+      }).then(function(elements) {
+        var result = [];
+        for (var i=0;i<elements.length;i++) {
+          result.push(new IdentifiedElement(elements[i]));
+        }
+        self._elementsByQuery[query]=result;
+        return self.getMap().fetchIdentifiedElements(result, true);
+      }).then(function(){
+        resolve(self._elementsByQuery[query]);
+      }).catch(reject);
+    }
+  });
+};
+
+SearchDbOverlay.prototype.searchByQuery = function(originalQuery, perfect) {
+  var self = this;
+  return new Promise(function(resolve, reject) {
+    var queries = [];
+    if (perfect) {
+      queries.push(originalQuery);
+    } else {
+      queries = self.splitQuery(originalQuery);
+    }
+    
+    var encodedQueries = [];
+    var promises = [];
+    for (var i=0;i<queries.length;i++) {
+      encodedQueries.push(encodeQuery(SearchDbOverlay.QueryType.SEARCH_BY_QUERY, queries[i], perfect));
+      promises.push(self.searchBySingleQuery(queries[i], perfect));
+    }
+    self.setQueries(encodedQueries);
+    
+    var res;
+    Promise.all(promises).then(function(results){
+      res =results;
+      return self.callListeners('onSearch');
+    }).then(function(){
+      resolve(res);
+    }).catch(reject);
+  });
+};
+
 SearchDbOverlay.prototype.setQueries = function(queries){
   this._queries = queries;
 };
diff --git a/frontend-js/src/test/js/gui/SearchPanel-test.js b/frontend-js/src/test/js/gui/SearchPanel-test.js
index 75413d3dd7..5cf34a7bba 100644
--- a/frontend-js/src/test/js/gui/SearchPanel-test.js
+++ b/frontend-js/src/test/js/gui/SearchPanel-test.js
@@ -45,11 +45,28 @@ describe('SearchPanel', function() {
     return searchDbOverlay.searchByCoordinates(map.getModel(), new google.maps.Point(26547.33, 39419.29)).then(
         function(results) {
           assert.equal(logger.getWarnings().length, 0);
-          logger.debug(div.innerHTML);
           assert.ok(div.innerHTML.indexOf("Reaction") >= 0);
         });
   });
 
+  it('on searchResults changed 2', function() {
+    var div = helper.createSearchTab();
+    var map = helper.createCustomMap();
+    map.getModel().setId(15781);
+    var searchDbOverlay = helper.createSearchDbOverlay(map);
+
+    new SearchPanel({
+      element : div,
+      customMap : map
+    });
+
+    return searchDbOverlay.searchByQuery("s1",false).then(
+        function(results) {
+          assert.equal(logger.getWarnings().length, 0);
+          assert.ok(div.innerHTML.indexOf("s1") >= 0);
+        });
+  });
+
   it('createAliasElement', function() {
     var aliasObj = {
       symbol : "S1_SYMB",
diff --git a/frontend-js/src/test/js/helper.js b/frontend-js/src/test/js/helper.js
index d5ff0f4ca1..95977897de 100644
--- a/frontend-js/src/test/js/helper.js
+++ b/frontend-js/src/test/js/helper.js
@@ -29,6 +29,19 @@ Helper.prototype.createSearchTab = function() {
   searchQueryDiv.setAttribute("name", "searchQuery");
   result.appendChild(searchQueryDiv);
 
+  var searchButton = document.createElement("img");
+  searchButton.setAttribute("name", "searchButton");
+  searchQueryDiv.appendChild(searchButton);
+
+  var searchInput = document.createElement("input");
+  searchInput.setAttribute("name", "searchInput");
+  searchQueryDiv.appendChild(searchInput);
+
+  var searchPerfectMatch = document.createElement("input");
+  searchPerfectMatch.type = "checkbox";
+  searchPerfectMatch.setAttribute("name", "searchPerfectMatch");
+  searchQueryDiv.appendChild(searchPerfectMatch);
+
   var searchResultsDiv = document.createElement("div");
   searchResultsDiv.setAttribute("name", "searchResults");
   result.appendChild(searchResultsDiv);
diff --git a/frontend-js/src/test/js/map/overlay/OverlayCollection-test.js b/frontend-js/src/test/js/map/overlay/OverlayCollection-test.js
index bf88db4244..ee9864aec7 100644
--- a/frontend-js/src/test/js/map/overlay/OverlayCollection-test.js
+++ b/frontend-js/src/test/js/map/overlay/OverlayCollection-test.js
@@ -123,4 +123,55 @@ describe('OverlayCollection', function() {
 
     assert.deepEqual([], data);
   });
+
+  it("splitQuery", function() {
+    var map = helper.createCustomMap();
+    
+    var oc = helper.createDbOverlay(map);
+    
+    var result = oc.splitQuery("test q");
+    assert.equal(result[0], "test q");
+  });
+  
+  it("splitQuery 2", function() {
+    var map = helper.createCustomMap();
+    
+    var oc = helper.createDbOverlay(map);
+    
+    var result = oc.splitQuery("test,q");
+    assert.equal(result[0], "test");
+    assert.equal(result[1], "q");
+  });
+  
+  it("splitQuery 3", function() {
+    var map = helper.createCustomMap();
+    
+    var oc = helper.createDbOverlay(map);
+    
+    var result = oc.splitQuery("test;q");
+    assert.equal(result[0], "test");
+    assert.equal(result[1], "q");
+  });
+  
+  it("splitQuery 4", function() {
+    var map = helper.createCustomMap();
+    
+    var oc = helper.createDbOverlay(map);
+    
+    var result = oc.splitQuery("test;q,bla");
+    assert.equal(result[0], "test");
+    assert.equal(result[1], "q,bla");
+    assert.equal(result.length,2)
+  });
+  
+  it("splitQuery 5", function() {
+    var map = helper.createCustomMap();
+    
+    var oc = helper.createDbOverlay(map);
+    
+    var result = oc.splitQuery("test;q,bla", true);
+    assert.equal(result[0], "test");
+    assert.equal(result[1], "q,bla");
+    assert.equal(result[2], "test;q,bla");
+  });
 });
diff --git a/frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329158,329159,329169,329173,329175,329177&projectId=sample&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329158,329159,329169,329173,329175,329177&projectId=sample&token=MOCK_TOKEN_ID&
new file mode 100644
index 0000000000..a11f751dd6
--- /dev/null
+++ b/frontend-js/testFiles/apiCalls/project/getElements/columns=&id=329158,329159,329169,329173,329175,329177&projectId=sample&token=MOCK_TOKEN_ID&
@@ -0,0 +1 @@
+[{"formerSymbols":[],"references":[],"modelId":15781,"synonyms":[],"description":"","type":"Unknown","name":"s11","bounds":{"x":105.0,"y":203.5,"width":70.0,"height":25.0},"id":329169},{"formerSymbols":[],"references":[],"modelId":15781,"synonyms":[],"description":"description of S1\r\nthird line","type":"Protein","name":"s1","bounds":{"x":12.0,"y":6.0,"width":80.0,"height":40.0},"id":329173},{"formerSymbols":[],"references":[],"modelId":15781,"synonyms":[],"description":"","type":"Drug","name":"s10","bounds":{"x":0.0,"y":186.0,"width":80.0,"height":30.0},"id":329177},{"formerSymbols":[],"references":[],"modelId":15781,"synonyms":[],"description":"","type":"Protein","name":"s14","bounds":{"x":279.0,"y":233.0,"width":80.0,"height":40.0},"id":329159},{"formerSymbols":[],"references":[],"modelId":15781,"synonyms":[],"description":"","type":"Degraded","name":"s13","bounds":{"x":401.0,"y":235.0,"width":30.0,"height":30.0},"id":329175},{"formerSymbols":[],"references":[],"modelId":15781,"synonyms":[],"description":"","type":"Complex","name":"s12","bounds":{"x":271.0,"y":207.0,"width":101.0,"height":164.0},"id":329158}]
\ No newline at end of file
diff --git a/frontend-js/testFiles/apiCalls/project/getElementsByQuery/perfectMatch=false&projectId=sample&query=s1&token=MOCK_TOKEN_ID& b/frontend-js/testFiles/apiCalls/project/getElementsByQuery/perfectMatch=false&projectId=sample&query=s1&token=MOCK_TOKEN_ID&
new file mode 100644
index 0000000000..0a39afdd5d
--- /dev/null
+++ b/frontend-js/testFiles/apiCalls/project/getElementsByQuery/perfectMatch=false&projectId=sample&query=s1&token=MOCK_TOKEN_ID&
@@ -0,0 +1 @@
+[{"modelId":15781,"id":329173,"type":"ALIAS"},{"modelId":15781,"id":329169,"type":"ALIAS"},{"modelId":15781,"id":329177,"type":"ALIAS"},{"modelId":15781,"id":329159,"type":"ALIAS"},{"modelId":15781,"id":329175,"type":"ALIAS"},{"modelId":15781,"id":329158,"type":"ALIAS"}]
\ No newline at end of file
-- 
GitLab