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

Proposed by Ben Hutchings
Status: Superseded
Proposed branch: lp:~ben-hutchings/ensoft-sextant/autocomplete-fix
Merge into: lp:ensoft-sextant
Diff against target: 214 lines (+80/-30)
4 files modified
resources/sextant/web/interface.html (+6/-5)
resources/sextant/web/queryjavascript.js (+46/-15)
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 Pending
Review via email: mp+241434@code.launchpad.net

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

This proposal has been superseded by a proposal from 2014-11-13.

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).

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.

35. By Ben Hutchings

fixed missing var in javascript, fixed unused global variables in python.

36. By Ben Hutchings

fixed bug by which timeout was never being tripped

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-13 17:26:43 +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-13 17:26:43 +0000
37@@ -6,42 +6,73 @@
38 //server or function names from a specific program.
39
40
41-function get_names_for_autocomplete(info_needed){
42+var timeout = null;
43+
44+
45+function get_names_for_autocomplete(info_needed, search){
46 //Function queries to database to create a list
47 //which is used to populate the auto-complete text boxes.
48+ if (typeof search == 'undefined') {
49+ search = "";
50+ }
51+
52 var xmlhttp = new XMLHttpRequest();
53 xmlhttp.onreadystatechange = function(){
54 if (xmlhttp.status = 200){
55 var values_list = xmlhttp.responseText;
56+ var do_timeout = false;
57 if (values_list != "") {
58 values_list = JSON.parse(values_list);
59 if (info_needed =='programs'){
60 //We need to populate the program names list
61 add_options("program_names", values_list);
62 }
63- if (info_needed =='funcs'){
64- //We need to populate the functions list for the arguments
65- add_options("function_names", values_list);
66- }
67+ if (info_needed == 'funcs_with_timeout_1') {
68+ add_options("function_names_1", values_list);
69+ do_timeout = true;
70+ }
71+ if (info_needed == 'funcs_with_timeout_2') {
72+ add_options("function_names_2", values_list);
73+ do_timeout = true;
74+ }
75 }
76 }
77 }
78 if (info_needed == 'programs'){
79 var string = "/database_properties?query=" + info_needed + "&program_name=";
80+ xmlhttp.open("GET", string, true);
81+ xmlhttp.send();
82 }
83 else{
84- var string = "/database_properties?query=" + "functions" +
85- "&program_name=" + document.getElementById("program_name").value;
86- if (info_needed == 'programs'){
87- var string = "/database_properties?query=" +
88- info_needed + "&program_name=" + prog_name;
89- }
90+ var target = null;
91+
92+ if (info_needed == 'funcs_with_timeout_1') {
93+ target = 'function_1';
94+ }
95+ else {
96+ target = 'function_2';
97+ }
98+
99+ timeoutfn = function() {
100+ var string = "/database_properties?query=" + "functions" +
101+ "&program_name=" + document.getElementById("program_name").value +
102+ "&search=" + document.getElementById(target).value;
103+ xmlhttp.open("GET", string, true);
104+ xmlhttp.send();
105+ timeout = null;
106+ };
107+
108+ if (do_timeout == true && timeout === null) {
109+ timeout = window.setTimeout(timeoutfn, 1000);
110+ } else if (do_timeout == true) {
111+ window.clearTimeout(timeout);
112+ timeout = window.setTimeout(timeoutfn, 1000);
113+ } else {
114+ timeoutfn();
115+ }
116+
117 //"GET" information from the specified url (string)
118- xmlhttp.open("GET", string, true);
119- xmlhttp.send();
120 }
121- xmlhttp.open("GET", string, true);
122- xmlhttp.send();
123 }
124
125
126
127=== modified file 'src/sextant/db_api.py'
128--- src/sextant/db_api.py 2014-10-23 11:15:48 +0000
129+++ src/sextant/db_api.py 2014-11-13 17:26:43 +0000
130@@ -832,7 +832,7 @@
131 result = self._db.query(q, returns=neo4jrestclient.Node)
132 return bool(result)
133
134- def get_function_names(self, program_name):
135+ def get_function_names(self, program_name, search, max_funcs):
136 """
137 Execute query to retrieve a list of all functions in the program.
138 Any of the output names can be used verbatim in any SextantConnection
139@@ -845,8 +845,13 @@
140 if not validate_query(program_name):
141 return set()
142
143- q = (' MATCH (:program {{name: "{}"}})-[:subject]->(f:func)'
144- ' RETURN f.name').format(program_name)
145+ if not search:
146+ q = (' MATCH (:program {{name: "{}"}})-[:subject]->(f:func)'
147+ ' RETURN f.name LIMIT {}').format(program_name, max_funcs)
148+ else:
149+ q = (' MATCH (:program {{name: "{}"}})-[:subject]->(f:func)'
150+ ' WHERE f.name =~ ".*{}.*" RETURN f.name LIMIT {}'
151+ .format(program_name, search, max_funcs))
152 return {func[0] for func in self._db.query(q)}
153
154 def get_all_functions_called(self, program_name, function_calling):
155
156=== modified file 'src/sextant/web/server.py'
157--- src/sextant/web/server.py 2014-10-13 15:09:08 +0000
158+++ src/sextant/web/server.py 2014-11-13 17:26:43 +0000
159@@ -29,6 +29,10 @@
160 # global SextantConnection object which deals with the port forwarding
161 CONNECTION = None
162
163+# The maximum number of matches to display for autocomplete. If more than
164+# this are matched, nothing will be returned.
165+AUTOCOMPLETE_NAMES_LIMIT = 75
166+
167 RESPONSE_CODE_OK = 200
168 RESPONSE_CODE_BAD_REQUEST = 400
169 RESPONSE_CODE_NOT_FOUND = 404
170@@ -218,8 +222,15 @@
171 return CONNECTION.get_program_names()
172
173 @staticmethod
174- def _get_function_names(program_name):
175- return CONNECTION.get_function_names(program_name)
176+ def _get_function_names(program_name, search=""):
177+ # For the returned data, we must allow one more name than
178+ # the maximum so that we know if the maximum was exceeded rather
179+ # than only met.
180+ max_funcs = AUTOCOMPLETE_NAMES_LIMIT + 1
181+ programs = CONNECTION.programs_with_metadata()
182+ result = CONNECTION.get_function_names(program_name, search, max_funcs)
183+ return result if len(result) < max_funcs else set()
184+
185
186 @defer.inlineCallbacks
187 def _render_GET(self, request):
188@@ -247,19 +258,21 @@
189 request.finish()
190 defer.returnValue(None)
191 program_name = request.args['program_name'][0]
192+ search = request.args.get('search', [''])[0]
193
194- funcnames = yield deferToThread(self._get_function_names, program_name)
195+ funcnames = yield deferToThread(self._get_function_names, program_name, search)
196 if funcnames is None:
197 request.setResponseCode(404)
198 request.setHeader("content-type", "text/plain")
199 request.write("No program with name %s was found in the Sextant." % escape(program_name))
200 request.finish()
201 defer.returnValue(None)
202+ else:
203+ request.setHeader("content-type", "application/json")
204+ request.write(json.dumps(list(funcnames)))
205
206- request.setHeader("content-type", "application/json")
207- request.write(json.dumps(list(funcnames)))
208- request.finish()
209- defer.returnValue(None)
210+ request.finish()
211+ defer.returnValue(None)
212
213 else:
214 request.setResponseCode(400)

Subscribers

People subscribed via source and target branches