Merge lp:~ben-hutchings/ensoft-sextant/autocomplete-fix into lp:ensoft-sextant

Proposed by Ben Hutchings
Status: Merged
Approved by: Robert
Approved revision: 38
Merged at revision: 31
Proposed branch: lp:~ben-hutchings/ensoft-sextant/autocomplete-fix
Merge into: lp:ensoft-sextant
Diff against target: 344 lines (+112/-69)
4 files modified
resources/sextant/web/interface.html (+6/-5)
resources/sextant/web/queryjavascript.js (+78/-54)
src/sextant/db_api.py (+8/-3)
src/sextant/web/server.py (+20/-7)
To merge this branch: bzr merge lp:~ben-hutchings/ensoft-sextant/autocomplete-fix
Reviewer Review Type Date Requested Status
Robert Approve
Review via email: mp+242486@code.launchpad.net

This proposal supersedes a proposal from 2014-11-19.

Commit message

Web gui now has a hard limit on how many functions it is willing to get for the autocomplete menu. If there are more than this number of functions in the program, the empty list will be returned. Maximum number of functions is set to 75 at present - more than this leads to drop down lists not displaying properly sometimes.

Substring search in function names. Debouncing (one second timer at present).

Fixed bug that was completely stopping the autocomplete from being called!

Description of the change

Web gui now has a hard limit on how many functions it is willing to get for the autocomplete menu. If there are more than this number of functions in the program, the empty list will be returned. Maximum number of functions is set to 75 at present - more than this leads to drop down lists not displaying properly sometimes.

Substring search in function names. Debouncing (one second timer at present).

Fixed bug that was completely stopping the autocomplete from being called!

To post a comment you must log in.
Revision history for this message
Ben Hutchings (ben-hutchings) wrote : Posted in a previous version of this proposal

Also the limit is hardcoded in web/server.py, not db_api.py.

Revision history for this message
Robert (rjwills) : Posted in a previous version of this proposal
review: Approve
38. By Ben Hutchings

indentation fix

Revision history for this message
Robert (rjwills) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'resources/sextant/web/interface.html'
--- resources/sextant/web/interface.html 2014-09-04 09:46:18 +0000
+++ resources/sextant/web/interface.html 2014-11-21 12:51:06 +0000
@@ -17,8 +17,8 @@
17 <div class="toolbar">17 <div class="toolbar">
18 <div>18 <div>
19 Program: 19 Program:
20 <input list="program_names" id="program_name" class="textbox"20 <input list="program_names" id="program_name" class="textbox" style="width: 150px" />
21 onblur="get_names_for_autocomplete('funcs')" style="width: 150px" />21 <!-- onblur="get_names_for_autocomplete('funcs')" -->
22 <datalist id="program_names"></datalist>22 <datalist id="program_names"></datalist>
2323
24 <select id="query_list" class="dropdown" onchange="display_when();">24 <select id="query_list" class="dropdown" onchange="display_when();">
@@ -44,13 +44,14 @@
44 </div>44 </div>
45 <div id="toolbar-row2" style="margin-left: 234px;">45 <div id="toolbar-row2" style="margin-left: 234px;">
46 <!--list to populate arguments. Updates when program name is specified-->46 <!--list to populate arguments. Updates when program name is specified-->
47 <datalist id="function_names"></datalist>47 <datalist id="function_names_1"></datalist>
48 <datalist id="function_names_2"></datalist>
4849
49 <span id="argument_1" style="size:20;"></span>50 <span id="argument_1" style="size:20;"></span>
50 <input list="function_names" id="function_1" class="textbox" style="size:20;"></input>51 <input list="function_names_1" autocomplete="off" id="function_1" class="textbox" style="size:20;" onkeyup="get_names_for_autocomplete('funcs_with_timeout_1')"></input>
5152
52 <span id="argument_2" style="size:20;"></span>53 <span id="argument_2" style="size:20;"></span>
53 <input list="function_names" id="function_2" class="textbox" style="size:20;"></input>54 <input list="function_names_2" autocomplete="off" id="function_2" class="textbox" style="size:20;" onkeyup="get_names_for_autocomplete('funcs_with_timeout_2')"></input>
54 </div>55 </div>
55 </div>56 </div>
5657
5758
=== modified file 'resources/sextant/web/queryjavascript.js'
--- resources/sextant/web/queryjavascript.js 2014-09-29 21:08:33 +0000
+++ resources/sextant/web/queryjavascript.js 2014-11-21 12:51:06 +0000
@@ -6,54 +6,84 @@
6//server or function names from a specific program.6//server or function names from a specific program.
77
88
9function get_names_for_autocomplete(info_needed){9var timeout = null;
10var do_timeout = false;
11
12
13function get_names_for_autocomplete(info_needed, search) {
10 //Function queries to database to create a list 14 //Function queries to database to create a list
11 //which is used to populate the auto-complete text boxes.15 //which is used to populate the auto-complete text boxes.
16 if (typeof search == 'undefined') {
17 search = "";
18 }
19
12 var xmlhttp = new XMLHttpRequest();20 var xmlhttp = new XMLHttpRequest();
13 xmlhttp.onreadystatechange = function(){21 xmlhttp.onreadystatechange = function() {
14 if (xmlhttp.status = 200){22 if (xmlhttp.status = 200) {
15 var values_list = xmlhttp.responseText;23 var values_list = xmlhttp.responseText;
16 if (values_list != "") {24 if (values_list != "") {
17 values_list = JSON.parse(values_list);25 values_list = JSON.parse(values_list);
18 if (info_needed =='programs'){26 if (info_needed =='programs') {
19 //We need to populate the program names list27 //We need to populate the program names list
20 add_options("program_names", values_list);28 add_options("program_names", values_list);
21 }29 do_timeout = false;
22 if (info_needed =='funcs'){30 }
23 //We need to populate the functions list for the arguments31 if (info_needed == 'funcs_with_timeout_1') {
24 add_options("function_names", values_list);32 add_options("function_names_1", values_list);
25 } 33 do_timeout = true;
34 }
35 if (info_needed == 'funcs_with_timeout_2') {
36 add_options("function_names_2", values_list);
37 do_timeout = true;
38 }
26 }39 }
27 }40 }
28 }41 }
29 if (info_needed == 'programs'){42
43 if (info_needed == 'programs') {
30 var string = "/database_properties?query=" + info_needed + "&program_name=";44 var string = "/database_properties?query=" + info_needed + "&program_name=";
31 }45 xmlhttp.open("GET", string, true);
32 else{46 xmlhttp.send();
33 var string = "/database_properties?query=" + "functions" + 47 } else {
34 "&program_name=" + document.getElementById("program_name").value;48 var target = null;
35 if (info_needed == 'programs'){49
36 var string = "/database_properties?query=" + 50 if (info_needed == 'funcs_with_timeout_1') {
37 info_needed + "&program_name=" + prog_name;51 target = 'function_1';
38 }52 } else {
53 target = 'function_2';
54 }
55
56 var timeoutfn = function() {
57 var string = "/database_properties?query=" + "functions" +
58 "&program_name=" + document.getElementById("program_name").value +
59 "&search=" + document.getElementById(target).value;
60 xmlhttp.open("GET", string, true);
61 xmlhttp.send();
62 timeout = null;
63 };
64
65 if (do_timeout == true && timeout === null) {
66 timeout = window.setTimeout(timeoutfn, 1000);
67 } else if (do_timeout == true) {
68 window.clearTimeout(timeout);
69 timeout = window.setTimeout(timeoutfn, 1000);
70 } else {
71 timeoutfn();
72 }
73
39 //"GET" information from the specified url (string)74 //"GET" information from the specified url (string)
40 xmlhttp.open("GET", string, true);
41 xmlhttp.send();
42 }75 }
43 xmlhttp.open("GET", string, true);
44 xmlhttp.send();
45}76}
4677
4778
48function add_options(selectedlist, values_list){79function add_options(selectedlist, values_list) {
49 //Adds all the options obtained from the list of program 80 //Adds all the options obtained from the list of program
50 //names or function names to an auto complete drop-down box81 //names or function names to an auto complete drop-down box
51 var options = ''82 var options = ''
52 if (values_list.length == 1 || values_list.length ==0){83 if (values_list.length == 1 || values_list.length ==0) {
53 options += '<option value="'+values_list+'"/>';84 options += '<option value="'+values_list+'"/>';
54 }85 } else {
55 else{86 for (var i=0; i < values_list.length;++i) {
56 for (var i=0; i < values_list.length;++i){
57 options += '<option value="'+values_list[i]+'"/>';87 options += '<option value="'+values_list[i]+'"/>';
58 }88 }
59 }89 }
@@ -84,48 +114,47 @@
84}114}
85115
86 116
87function display_when(){117function display_when() {
88 //For each query specifies when auto-complete text boxes should be made 118 //For each query specifies when auto-complete text boxes should be made
89 //visible or invisible and makes them read only119 //visible or invisible and makes them read only
90 var query_list = document.getElementById("query_list");120 var query_list = document.getElementById("query_list");
91121
92 var no_functions = new Array();122 var no_functions = new Array();
93 var prog_name = document.getElementById("program_name").value;123 var prog_name = document.getElementById("program_name").value;
94 if (query_list.options[query_list.selectedIndex].value == "functions_calling"){124 if (query_list.options[query_list.selectedIndex].value == "functions_calling") {
95 set_arguments("Function being called", "");125 set_arguments("Function being called", "");
96 }126 }
97 if (query_list.options[query_list.selectedIndex].value == "functions_called_by"){127 if (query_list.options[query_list.selectedIndex].value == "functions_called_by") {
98 set_arguments("Function calling", "");128 set_arguments("Function calling", "");
99 }129 }
100 if (query_list.options[query_list.selectedIndex].value == "all_call_paths"){130 if (query_list.options[query_list.selectedIndex].value == "all_call_paths") {
101 set_arguments("Function calling", "Function being called");131 set_arguments("Function calling", "Function being called");
102 }132 }
103 if (query_list.options[query_list.selectedIndex].value == "shortest_call_path"){133 if (query_list.options[query_list.selectedIndex].value == "shortest_call_path") {
104 set_arguments("Function calling", "Function being called");134 set_arguments("Function calling", "Function being called");
105 }135 }
106 if (query_list.options[query_list.selectedIndex].value == "whole_program") {136 if (query_list.options[query_list.selectedIndex].value == "whole_program") {
107 set_arguments("", "");137 set_arguments("", "");
108 }138 }
109 if (query_list.options[query_list.selectedIndex].value == "function_names"){139 if (query_list.options[query_list.selectedIndex].value == "function_names") {
110 set_arguments("", "");140 set_arguments("", "");
111 }141 }
112}142}
113143
114144
115145
116function execute_query(){146function execute_query() {
117 document.getElementById("output_image").src = "";147 document.getElementById("output_image").src = "";
118148
119 //Returns error in alert window if query not executed properly, 149 //Returns error in alert window if query not executed properly,
120 //otherwise performs the query and outputs it150 //otherwise performs the query and outputs it
121 show_item("please-wait");151 show_item("please-wait");
122 var query_id = document.getElementById("query_list").value;152 var query_id = document.getElementById("query_list").value;
123 if (query_id == "function_names"){153 if (query_id == "function_names") {
124 //url for page containing all function names154 //url for page containing all function names
125 var string = "/database_properties?program_name=" + 155 var string = "/database_properties?program_name=" +
126 document.getElementById("program_name").value + "&query=functions";156 document.getElementById("program_name").value + "&query=functions";
127 }157 } else {
128 else{
129 //If not function names we will want a graph as an output; 158 //If not function names we will want a graph as an output;
130 //url returns svg file of graph.159 //url returns svg file of graph.
131 // We use a random number argument to prevent caching.160 // We use a random number argument to prevent caching.
@@ -141,11 +170,11 @@
141 var xmlhttp = new XMLHttpRequest();170 var xmlhttp = new XMLHttpRequest();
142 xmlhttp.open("GET", string, true);171 xmlhttp.open("GET", string, true);
143 xmlhttp.send();172 xmlhttp.send();
144 xmlhttp.onreadystatechange = function(){173 xmlhttp.onreadystatechange = function() {
145 if (xmlhttp.readyState == 4 && xmlhttp.status == 200){174 if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
146 //readyState == 4 means query has finished executing.175 //readyState == 4 means query has finished executing.
147 //status == 200 means "GET"ing has been successful.176 //status == 200 means "GET"ing has been successful.
148 if (query_id == "function_names"){177 if (query_id == "function_names") {
149 //Text output displayed in paragraph.178 //Text output displayed in paragraph.
150 document.getElementById("function_names_output").innerHTML = 179 document.getElementById("function_names_output").innerHTML =
151 xmlhttp.responseText;180 xmlhttp.responseText;
@@ -158,24 +187,19 @@
158 var dataUrl = "data:image/svg+xml;base64," + btoa(xmlhttp.responseText);187 var dataUrl = "data:image/svg+xml;base64," + btoa(xmlhttp.responseText);
159 document.getElementById("output_image").src = dataUrl;188 document.getElementById("output_image").src = dataUrl;
160 }189 }
161 }190 } else if (xmlhttp.readyState == 4 && xmlhttp.status == 400) {
162 else if (xmlhttp.readyState == 4 && xmlhttp.status == 400){191 //Error occurred during query; display response.
163 //Error occurred during query; display response.192 show_error(xmlhttp.responseText);
164 show_error(xmlhttp.responseText);193 } else if(xmlhttp.readyState == 4 && xmlhttp.status == 404) {
165 }194 //Error occurred during query; display response.
166 else if(xmlhttp.readyState == 4 && xmlhttp.status == 404){195 show_error(xmlhttp.responseText);
167 //Error occurred during query; display response.196 } else if(xmlhttp.readyState ==4 && xmlhttp.status == 204) {
168 show_error(xmlhttp.responseText);
169 }
170 else if(xmlhttp.readyState ==4 && xmlhttp.status == 204){
171 //Query executed correctly but graph returned is empty197 //Query executed correctly but graph returned is empty
172 show_error("Graph returned was empty");198 show_error("Graph returned was empty");
173 }199 } else if (xmlhttp.readyState == 4 && xmlhttp.status == 502) {
174 else if (xmlhttp.readyState == 4 && xmlhttp.status == 502) {
175 //Error occurs if Neo4j isn't running200 //Error occurs if Neo4j isn't running
176 show_error("Bad Gateway received - are you sure the database server is running?");201 show_error("Bad Gateway received - are you sure the database server is running?");
177 }202 } else if(xmlhttp.readyState ==4) {
178 else if(xmlhttp.readyState ==4){
179 //query executed correctly203 //query executed correctly
180 show_error("An unknown error occurred");204 show_error("An unknown error occurred");
181 }205 }
182206
=== modified file 'src/sextant/db_api.py'
--- src/sextant/db_api.py 2014-10-23 11:15:48 +0000
+++ src/sextant/db_api.py 2014-11-21 12:51:06 +0000
@@ -832,7 +832,7 @@
832 result = self._db.query(q, returns=neo4jrestclient.Node)832 result = self._db.query(q, returns=neo4jrestclient.Node)
833 return bool(result)833 return bool(result)
834834
835 def get_function_names(self, program_name):835 def get_function_names(self, program_name, search, max_funcs):
836 """836 """
837 Execute query to retrieve a list of all functions in the program.837 Execute query to retrieve a list of all functions in the program.
838 Any of the output names can be used verbatim in any SextantConnection838 Any of the output names can be used verbatim in any SextantConnection
@@ -845,8 +845,13 @@
845 if not validate_query(program_name):845 if not validate_query(program_name):
846 return set()846 return set()
847847
848 q = (' MATCH (:program {{name: "{}"}})-[:subject]->(f:func)'848 if not search:
849 ' RETURN f.name').format(program_name)849 q = (' MATCH (:program {{name: "{}"}})-[:subject]->(f:func)'
850 ' RETURN f.name LIMIT {}').format(program_name, max_funcs)
851 else:
852 q = (' MATCH (:program {{name: "{}"}})-[:subject]->(f:func)'
853 ' WHERE f.name =~ ".*{}.*" RETURN f.name LIMIT {}'
854 .format(program_name, search, max_funcs))
850 return {func[0] for func in self._db.query(q)}855 return {func[0] for func in self._db.query(q)}
851856
852 def get_all_functions_called(self, program_name, function_calling):857 def get_all_functions_called(self, program_name, function_calling):
853858
=== modified file 'src/sextant/web/server.py'
--- src/sextant/web/server.py 2014-10-13 15:09:08 +0000
+++ src/sextant/web/server.py 2014-11-21 12:51:06 +0000
@@ -29,6 +29,10 @@
29# global SextantConnection object which deals with the port forwarding29# global SextantConnection object which deals with the port forwarding
30CONNECTION = None30CONNECTION = None
3131
32# The maximum number of matches to display for autocomplete. If more than
33# this are matched, nothing will be returned.
34AUTOCOMPLETE_NAMES_LIMIT = 75
35
32RESPONSE_CODE_OK = 20036RESPONSE_CODE_OK = 200
33RESPONSE_CODE_BAD_REQUEST = 40037RESPONSE_CODE_BAD_REQUEST = 400
34RESPONSE_CODE_NOT_FOUND = 40438RESPONSE_CODE_NOT_FOUND = 404
@@ -218,8 +222,15 @@
218 return CONNECTION.get_program_names()222 return CONNECTION.get_program_names()
219223
220 @staticmethod224 @staticmethod
221 def _get_function_names(program_name):225 def _get_function_names(program_name, search=""):
222 return CONNECTION.get_function_names(program_name)226 # For the returned data, we must allow one more name than
227 # the maximum so that we know if the maximum was exceeded rather
228 # than only met.
229 max_funcs = AUTOCOMPLETE_NAMES_LIMIT + 1
230 programs = CONNECTION.programs_with_metadata()
231 result = CONNECTION.get_function_names(program_name, search, max_funcs)
232 return result if len(result) < max_funcs else set()
233
223234
224 @defer.inlineCallbacks235 @defer.inlineCallbacks
225 def _render_GET(self, request):236 def _render_GET(self, request):
@@ -247,19 +258,21 @@
247 request.finish()258 request.finish()
248 defer.returnValue(None)259 defer.returnValue(None)
249 program_name = request.args['program_name'][0]260 program_name = request.args['program_name'][0]
261 search = request.args.get('search', [''])[0]
250262
251 funcnames = yield deferToThread(self._get_function_names, program_name)263 funcnames = yield deferToThread(self._get_function_names, program_name, search)
252 if funcnames is None:264 if funcnames is None:
253 request.setResponseCode(404)265 request.setResponseCode(404)
254 request.setHeader("content-type", "text/plain")266 request.setHeader("content-type", "text/plain")
255 request.write("No program with name %s was found in the Sextant." % escape(program_name))267 request.write("No program with name %s was found in the Sextant." % escape(program_name))
256 request.finish()268 request.finish()
257 defer.returnValue(None)269 defer.returnValue(None)
270 else:
271 request.setHeader("content-type", "application/json")
272 request.write(json.dumps(list(funcnames)))
258273
259 request.setHeader("content-type", "application/json")274 request.finish()
260 request.write(json.dumps(list(funcnames)))275 defer.returnValue(None)
261 request.finish()
262 defer.returnValue(None)
263276
264 else:277 else:
265 request.setResponseCode(400)278 request.setResponseCode(400)

Subscribers

People subscribed via source and target branches