Merge lp:~ben-hutchings/ensoft-sextant/autocomplete-fix into lp:ensoft-sextant
- autocomplete-fix
- Merge into whiteline
Proposed by
Ben Hutchings
Status: | Superseded |
---|---|
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 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Robert | Pending | ||
Review via email: mp+242190@code.launchpad.net |
This proposal supersedes a proposal from 2014-11-13.
This proposal has been superseded by a proposal from 2014-11-21.
Commit message
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 | # |
Revision history for this message
Robert (rjwills) : Posted in a previous version of this proposal | # |
review:
Approve
- 37. By Ben Hutchings
-
whitespace changes, added missing var
- 38. By Ben Hutchings
-
indentation fix
Unmerged revisions
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'resources/sextant/web/interface.html' |
2 | --- resources/sextant/web/interface.html 2014-09-04 09:46:18 +0000 |
3 | +++ resources/sextant/web/interface.html 2014-11-21 12:32:15 +0000 |
4 | @@ -17,8 +17,8 @@ |
5 | <div class="toolbar"> |
6 | <div> |
7 | Program: |
8 | - <input list="program_names" id="program_name" class="textbox" |
9 | - onblur="get_names_for_autocomplete('funcs')" style="width: 150px" /> |
10 | + <input list="program_names" id="program_name" class="textbox" style="width: 150px" /> |
11 | + <!-- onblur="get_names_for_autocomplete('funcs')" --> |
12 | <datalist id="program_names"></datalist> |
13 | |
14 | <select id="query_list" class="dropdown" onchange="display_when();"> |
15 | @@ -44,13 +44,14 @@ |
16 | </div> |
17 | <div id="toolbar-row2" style="margin-left: 234px;"> |
18 | <!--list to populate arguments. Updates when program name is specified--> |
19 | - <datalist id="function_names"></datalist> |
20 | + <datalist id="function_names_1"></datalist> |
21 | + <datalist id="function_names_2"></datalist> |
22 | |
23 | <span id="argument_1" style="size:20;"></span> |
24 | - <input list="function_names" id="function_1" class="textbox" style="size:20;"></input> |
25 | + <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> |
26 | |
27 | <span id="argument_2" style="size:20;"></span> |
28 | - <input list="function_names" id="function_2" class="textbox" style="size:20;"></input> |
29 | + <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> |
30 | </div> |
31 | </div> |
32 | |
33 | |
34 | === modified file 'resources/sextant/web/queryjavascript.js' |
35 | --- resources/sextant/web/queryjavascript.js 2014-09-29 21:08:33 +0000 |
36 | +++ resources/sextant/web/queryjavascript.js 2014-11-21 12:32:15 +0000 |
37 | @@ -6,54 +6,84 @@ |
38 | //server or function names from a specific program. |
39 | |
40 | |
41 | -function get_names_for_autocomplete(info_needed){ |
42 | +var timeout = null; |
43 | +var do_timeout = false; |
44 | + |
45 | + |
46 | +function get_names_for_autocomplete(info_needed, search) { |
47 | //Function queries to database to create a list |
48 | //which is used to populate the auto-complete text boxes. |
49 | + if (typeof search == 'undefined') { |
50 | + search = ""; |
51 | + } |
52 | + |
53 | var xmlhttp = new XMLHttpRequest(); |
54 | - xmlhttp.onreadystatechange = function(){ |
55 | - if (xmlhttp.status = 200){ |
56 | + xmlhttp.onreadystatechange = function() { |
57 | + if (xmlhttp.status = 200) { |
58 | var values_list = xmlhttp.responseText; |
59 | if (values_list != "") { |
60 | values_list = JSON.parse(values_list); |
61 | - if (info_needed =='programs'){ |
62 | + if (info_needed =='programs') { |
63 | //We need to populate the program names list |
64 | add_options("program_names", values_list); |
65 | - } |
66 | - if (info_needed =='funcs'){ |
67 | - //We need to populate the functions list for the arguments |
68 | - add_options("function_names", values_list); |
69 | - } |
70 | + do_timeout = false; |
71 | + } |
72 | + if (info_needed == 'funcs_with_timeout_1') { |
73 | + add_options("function_names_1", values_list); |
74 | + do_timeout = true; |
75 | + } |
76 | + if (info_needed == 'funcs_with_timeout_2') { |
77 | + add_options("function_names_2", values_list); |
78 | + do_timeout = true; |
79 | + } |
80 | } |
81 | } |
82 | } |
83 | - if (info_needed == 'programs'){ |
84 | + |
85 | + if (info_needed == 'programs') { |
86 | var string = "/database_properties?query=" + info_needed + "&program_name="; |
87 | - } |
88 | - else{ |
89 | - var string = "/database_properties?query=" + "functions" + |
90 | - "&program_name=" + document.getElementById("program_name").value; |
91 | - if (info_needed == 'programs'){ |
92 | - var string = "/database_properties?query=" + |
93 | - info_needed + "&program_name=" + prog_name; |
94 | - } |
95 | + xmlhttp.open("GET", string, true); |
96 | + xmlhttp.send(); |
97 | + } else { |
98 | + var target = null; |
99 | + |
100 | + if (info_needed == 'funcs_with_timeout_1') { |
101 | + target = 'function_1'; |
102 | + } else { |
103 | + target = 'function_2'; |
104 | + } |
105 | + |
106 | + var timeoutfn = function() { |
107 | + var string = "/database_properties?query=" + "functions" + |
108 | + "&program_name=" + document.getElementById("program_name").value + |
109 | + "&search=" + document.getElementById(target).value; |
110 | + xmlhttp.open("GET", string, true); |
111 | + xmlhttp.send(); |
112 | + timeout = null; |
113 | + }; |
114 | + |
115 | + if (do_timeout == true && timeout === null) { |
116 | + timeout = window.setTimeout(timeoutfn, 1000); |
117 | + } else if (do_timeout == true) { |
118 | + window.clearTimeout(timeout); |
119 | + timeout = window.setTimeout(timeoutfn, 1000); |
120 | + } else { |
121 | + timeoutfn(); |
122 | + } |
123 | + |
124 | //"GET" information from the specified url (string) |
125 | - xmlhttp.open("GET", string, true); |
126 | - xmlhttp.send(); |
127 | } |
128 | - xmlhttp.open("GET", string, true); |
129 | - xmlhttp.send(); |
130 | } |
131 | |
132 | |
133 | -function add_options(selectedlist, values_list){ |
134 | +function add_options(selectedlist, values_list) { |
135 | //Adds all the options obtained from the list of program |
136 | //names or function names to an auto complete drop-down box |
137 | var options = '' |
138 | - if (values_list.length == 1 || values_list.length ==0){ |
139 | + if (values_list.length == 1 || values_list.length ==0) { |
140 | options += '<option value="'+values_list+'"/>'; |
141 | - } |
142 | - else{ |
143 | - for (var i=0; i < values_list.length;++i){ |
144 | + } else { |
145 | + for (var i=0; i < values_list.length;++i) { |
146 | options += '<option value="'+values_list[i]+'"/>'; |
147 | } |
148 | } |
149 | @@ -84,48 +114,47 @@ |
150 | } |
151 | |
152 | |
153 | -function display_when(){ |
154 | +function display_when() { |
155 | //For each query specifies when auto-complete text boxes should be made |
156 | //visible or invisible and makes them read only |
157 | var query_list = document.getElementById("query_list"); |
158 | |
159 | var no_functions = new Array(); |
160 | var prog_name = document.getElementById("program_name").value; |
161 | - if (query_list.options[query_list.selectedIndex].value == "functions_calling"){ |
162 | + if (query_list.options[query_list.selectedIndex].value == "functions_calling") { |
163 | set_arguments("Function being called", ""); |
164 | } |
165 | - if (query_list.options[query_list.selectedIndex].value == "functions_called_by"){ |
166 | + if (query_list.options[query_list.selectedIndex].value == "functions_called_by") { |
167 | set_arguments("Function calling", ""); |
168 | } |
169 | - if (query_list.options[query_list.selectedIndex].value == "all_call_paths"){ |
170 | + if (query_list.options[query_list.selectedIndex].value == "all_call_paths") { |
171 | set_arguments("Function calling", "Function being called"); |
172 | } |
173 | - if (query_list.options[query_list.selectedIndex].value == "shortest_call_path"){ |
174 | + if (query_list.options[query_list.selectedIndex].value == "shortest_call_path") { |
175 | set_arguments("Function calling", "Function being called"); |
176 | } |
177 | if (query_list.options[query_list.selectedIndex].value == "whole_program") { |
178 | set_arguments("", ""); |
179 | } |
180 | - if (query_list.options[query_list.selectedIndex].value == "function_names"){ |
181 | + if (query_list.options[query_list.selectedIndex].value == "function_names") { |
182 | set_arguments("", ""); |
183 | } |
184 | } |
185 | |
186 | |
187 | |
188 | -function execute_query(){ |
189 | +function execute_query() { |
190 | document.getElementById("output_image").src = ""; |
191 | |
192 | //Returns error in alert window if query not executed properly, |
193 | //otherwise performs the query and outputs it |
194 | show_item("please-wait"); |
195 | var query_id = document.getElementById("query_list").value; |
196 | - if (query_id == "function_names"){ |
197 | + if (query_id == "function_names") { |
198 | //url for page containing all function names |
199 | var string = "/database_properties?program_name=" + |
200 | document.getElementById("program_name").value + "&query=functions"; |
201 | - } |
202 | - else{ |
203 | + } else { |
204 | //If not function names we will want a graph as an output; |
205 | //url returns svg file of graph. |
206 | // We use a random number argument to prevent caching. |
207 | @@ -141,11 +170,11 @@ |
208 | var xmlhttp = new XMLHttpRequest(); |
209 | xmlhttp.open("GET", string, true); |
210 | xmlhttp.send(); |
211 | - xmlhttp.onreadystatechange = function(){ |
212 | - if (xmlhttp.readyState == 4 && xmlhttp.status == 200){ |
213 | + xmlhttp.onreadystatechange = function() { |
214 | + if (xmlhttp.readyState == 4 && xmlhttp.status == 200) { |
215 | //readyState == 4 means query has finished executing. |
216 | //status == 200 means "GET"ing has been successful. |
217 | - if (query_id == "function_names"){ |
218 | + if (query_id == "function_names") { |
219 | //Text output displayed in paragraph. |
220 | document.getElementById("function_names_output").innerHTML = |
221 | xmlhttp.responseText; |
222 | @@ -158,24 +187,19 @@ |
223 | var dataUrl = "data:image/svg+xml;base64," + btoa(xmlhttp.responseText); |
224 | document.getElementById("output_image").src = dataUrl; |
225 | } |
226 | - } |
227 | - else if (xmlhttp.readyState == 4 && xmlhttp.status == 400){ |
228 | - //Error occurred during query; display response. |
229 | - show_error(xmlhttp.responseText); |
230 | - } |
231 | - else if(xmlhttp.readyState == 4 && xmlhttp.status == 404){ |
232 | - //Error occurred during query; display response. |
233 | - show_error(xmlhttp.responseText); |
234 | - } |
235 | - else if(xmlhttp.readyState ==4 && xmlhttp.status == 204){ |
236 | + } else if (xmlhttp.readyState == 4 && xmlhttp.status == 400) { |
237 | + //Error occurred during query; display response. |
238 | + show_error(xmlhttp.responseText); |
239 | + } else if(xmlhttp.readyState == 4 && xmlhttp.status == 404) { |
240 | + //Error occurred during query; display response. |
241 | + show_error(xmlhttp.responseText); |
242 | + } else if(xmlhttp.readyState ==4 && xmlhttp.status == 204) { |
243 | //Query executed correctly but graph returned is empty |
244 | show_error("Graph returned was empty"); |
245 | - } |
246 | - else if (xmlhttp.readyState == 4 && xmlhttp.status == 502) { |
247 | + } else if (xmlhttp.readyState == 4 && xmlhttp.status == 502) { |
248 | //Error occurs if Neo4j isn't running |
249 | show_error("Bad Gateway received - are you sure the database server is running?"); |
250 | - } |
251 | - else if(xmlhttp.readyState ==4){ |
252 | + } else if(xmlhttp.readyState ==4) { |
253 | //query executed correctly |
254 | show_error("An unknown error occurred"); |
255 | } |
256 | |
257 | === modified file 'src/sextant/db_api.py' |
258 | --- src/sextant/db_api.py 2014-10-23 11:15:48 +0000 |
259 | +++ src/sextant/db_api.py 2014-11-21 12:32:15 +0000 |
260 | @@ -832,7 +832,7 @@ |
261 | result = self._db.query(q, returns=neo4jrestclient.Node) |
262 | return bool(result) |
263 | |
264 | - def get_function_names(self, program_name): |
265 | + def get_function_names(self, program_name, search, max_funcs): |
266 | """ |
267 | Execute query to retrieve a list of all functions in the program. |
268 | Any of the output names can be used verbatim in any SextantConnection |
269 | @@ -845,8 +845,13 @@ |
270 | if not validate_query(program_name): |
271 | return set() |
272 | |
273 | - q = (' MATCH (:program {{name: "{}"}})-[:subject]->(f:func)' |
274 | - ' RETURN f.name').format(program_name) |
275 | + if not search: |
276 | + q = (' MATCH (:program {{name: "{}"}})-[:subject]->(f:func)' |
277 | + ' RETURN f.name LIMIT {}').format(program_name, max_funcs) |
278 | + else: |
279 | + q = (' MATCH (:program {{name: "{}"}})-[:subject]->(f:func)' |
280 | + ' WHERE f.name =~ ".*{}.*" RETURN f.name LIMIT {}' |
281 | + .format(program_name, search, max_funcs)) |
282 | return {func[0] for func in self._db.query(q)} |
283 | |
284 | def get_all_functions_called(self, program_name, function_calling): |
285 | |
286 | === modified file 'src/sextant/web/server.py' |
287 | --- src/sextant/web/server.py 2014-10-13 15:09:08 +0000 |
288 | +++ src/sextant/web/server.py 2014-11-21 12:32:15 +0000 |
289 | @@ -29,6 +29,10 @@ |
290 | # global SextantConnection object which deals with the port forwarding |
291 | CONNECTION = None |
292 | |
293 | +# The maximum number of matches to display for autocomplete. If more than |
294 | +# this are matched, nothing will be returned. |
295 | +AUTOCOMPLETE_NAMES_LIMIT = 75 |
296 | + |
297 | RESPONSE_CODE_OK = 200 |
298 | RESPONSE_CODE_BAD_REQUEST = 400 |
299 | RESPONSE_CODE_NOT_FOUND = 404 |
300 | @@ -218,8 +222,15 @@ |
301 | return CONNECTION.get_program_names() |
302 | |
303 | @staticmethod |
304 | - def _get_function_names(program_name): |
305 | - return CONNECTION.get_function_names(program_name) |
306 | + def _get_function_names(program_name, search=""): |
307 | + # For the returned data, we must allow one more name than |
308 | + # the maximum so that we know if the maximum was exceeded rather |
309 | + # than only met. |
310 | + max_funcs = AUTOCOMPLETE_NAMES_LIMIT + 1 |
311 | + programs = CONNECTION.programs_with_metadata() |
312 | + result = CONNECTION.get_function_names(program_name, search, max_funcs) |
313 | + return result if len(result) < max_funcs else set() |
314 | + |
315 | |
316 | @defer.inlineCallbacks |
317 | def _render_GET(self, request): |
318 | @@ -247,19 +258,21 @@ |
319 | request.finish() |
320 | defer.returnValue(None) |
321 | program_name = request.args['program_name'][0] |
322 | + search = request.args.get('search', [''])[0] |
323 | |
324 | - funcnames = yield deferToThread(self._get_function_names, program_name) |
325 | + funcnames = yield deferToThread(self._get_function_names, program_name, search) |
326 | if funcnames is None: |
327 | request.setResponseCode(404) |
328 | request.setHeader("content-type", "text/plain") |
329 | request.write("No program with name %s was found in the Sextant." % escape(program_name)) |
330 | request.finish() |
331 | defer.returnValue(None) |
332 | + else: |
333 | + request.setHeader("content-type", "application/json") |
334 | + request.write(json.dumps(list(funcnames))) |
335 | |
336 | - request.setHeader("content-type", "application/json") |
337 | - request.write(json.dumps(list(funcnames))) |
338 | - request.finish() |
339 | - defer.returnValue(None) |
340 | + request.finish() |
341 | + defer.returnValue(None) |
342 | |
343 | else: |
344 | request.setResponseCode(400) |
Also the limit is hardcoded in web/server.py, not db_api.py.