Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(26)

Side by Side Diff: appengine/monorail/static/js/tracker/tracker-ac.js

Issue 1868553004: Open Source Monorail (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@master
Patch Set: Rebase Created 4 years, 8 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
1 /* Copyright 2016 The Chromium Authors. All Rights Reserved.
2 *
3 * Use of this source code is governed by a BSD-style
4 * license that can be found in the LICENSE file or at
5 * https://developers.google.com/open-source/licenses/bsd
6 */
7
8 /**
9 * This file contains the autocomplete configuration logic that is
10 * specific to the issue fields of Monorail. It depends on ac.js, our
11 * modified version of the autocomplete library.
12 */
13
14
15 /**
16 * This is an autocomplete store that holds well-known issue label
17 * values for the current project.
18 */
19 var TKR_labelStore;
20
21 /**
22 * This is an autocomplete store that holds issue components.
23 */
24 var TKR_componentListStore;
25
26 /**
27 * This is an autocomplete store that holds many different kinds of
28 * items that can be shown in the artifact search autocomplete.
29 */
30 var TKR_searchStore;
31
32 /**
33 * This is similar to TKR_searchStore, but does not include any suggestions
34 * to use the "me" keyword. Using "me" is not a good idea for project canned
35 * queries and filter rules.
36 */
37 var TKR_projectQueryStore;
38
39 /**
40 * This is an autocomplete store that holds items for the quick edit
41 * autocomplete.
42 */
43 // TODO(jrobbins): add options for fields and components.
44 var TKR_quickEditStore;
45
46 /**
47 * This is a list of label prefixes that each issue should only use once.
48 * E.g., each issue should only have one Priority-* label. We do not prevent
49 * the user from using multiple such labels, we just warn the user before
50 * he/she submits.
51 */
52 var TKR_exclPrefixes = [];
53
54 /**
55 * This is an autocomplete store that holds custom permission names that
56 * have already been used in this project.
57 */
58 var TKR_customPermissionsStore;
59
60
61 /**
62 * This is an autocomplete store that holds well-known issue status
63 * values for the current project.
64 */
65 var TKR_statusStore;
66
67
68 /**
69 * This is an autocomplete store that holds the usernames of all the
70 * members of the current project. This is used for autocomplete in
71 * the cc-list of an issue, where many user names can entered with
72 * commas between them.
73 */
74 var TKR_memberListStore;
75
76
77 /**
78 * This is an autocomplete store that holds the projects that the current
79 * user is contributor/member/owner of.
80 */
81 var TKR_projectStore;
82
83 /**
84 * This is an autocomplete store that holds the usernames of possible
85 * issue owners in the current project. The list of possible issue
86 * owners is the same as the list of project members, but the behavior
87 * of this autocompete store is different because the issue owner text
88 * field can only accept one value.
89 */
90 var TKR_ownerStore;
91
92
93 /**
94 * This is an autocomplete store that holds any list of string for choices.
95 */
96 var TKR_autoCompleteStore;
97
98
99 /**
100 * An array of autocomplete stores used for user-type custom fields.
101 */
102 var TKR_userAutocompleteStores = [];
103
104
105 /**
106 * This boolean controls whether odd-ball status and labels are treated as
107 * a warning or an error. Normally, it is False.
108 */
109 // TODO(jrobbins): split this into one option for statuses and one for labels.
110 var TKR_restrict_to_known;
111
112 /**
113 * This keeps track of the type of autocomplete feed that will be displayed.
114 * The type determines which search operators are offered to the user. E.g.,
115 * "filename:" only makes sense for downloads.
116 */
117 // TODO(jrobbins): remove, this seems unneeded now.
118 var TKR_autoCompleteFeedName;
119
120 /**
121 * This substitute function should be used for multi-valued autocomplete fields
122 * that are delimited by commas. When we insert an autocomplete value, replace
123 * an entire search term. Add a comma and a space after it if it is a complete
124 * search term.
125 */
126 function TKR_acSubstituteWithComma(inputValue, caret, completable, completion) {
127 var nextTerm = caret;
128 while (inputValue.charAt(nextTerm) != ' ' && nextTerm < inputValue.length) {
129 nextTerm++;
130 }
131 while (inputValue.charAt(nextTerm) == ' ' && nextTerm < inputValue.length) {
132 nextTerm++;
133 }
134 return inputValue.substring(0, caret - completable.length) +
135 completion.value + ', ' + inputValue.substring(nextTerm);
136 }
137
138 /**
139 * When the prefix starts with '*', return the complete set of all
140 * possible completions.
141 * @param {string} prefix If this starts with '*', return all possible
142 * completions. Otherwise return null.
143 * @param {Array} labelDefs The array of label names and docstrings.
144 * @returns Array of new _AC_Completions for each possible completion, or null.
145 */
146 function TKR_fullComplete(prefix, labelDefs) {
147 if (!prefix.startsWith('*')) return null;
148 var out = [];
149 for (var i = 0; i < labelDefs.length; i++) {
150 out.push(new _AC_Completion(labelDefs[i].name,
151 labelDefs[i].name,
152 labelDefs[i].doc));
153 }
154 return out;
155 }
156
157
158 /**
159 * Constucts a list of all completions for both open and closed
160 * statuses, with a header for each group.
161 * @param {string} prefix If starts with '*', return all possible completions,
162 * else return null.
163 * @param {Array} openStatusDefs The array of open status values and
164 * docstrings.
165 * @param {Array} closedStatusDefs The array of closed status values
166 * and docstrings.
167 * @returns Array of new _AC_Completions for each possible completion, or null.
168 */
169 function TKR_openClosedComplete(prefix, openStatusDefs, closedStatusDefs) {
170 if (!prefix.startsWith('*')) return null;
171 var out = [];
172 out.push({heading:'Open Statuses:'}); // TODO: i18n
173 for (var i = 0; i < openStatusDefs.length; i++) {
174 out.push(new _AC_Completion(openStatusDefs[i].name,
175 openStatusDefs[i].name,
176 openStatusDefs[i].doc));
177 }
178 out.push({heading:'Closed Statuses:'}); // TODO: i18n
179 for (var i = 0; i < closedStatusDefs.length; i++) {
180 out.push(new _AC_Completion(closedStatusDefs[i].name,
181 closedStatusDefs[i].name,
182 closedStatusDefs[i].doc));
183 }
184 return out;
185 }
186
187
188 /**
189 * An array of definitions of all well-known issue statuses. Each
190 * definition has the name of the status value, and a docstring that
191 * describes its meaning.
192 */
193 var TKR_statusWords = [];
194
195
196 /**
197 * Constuct a new autocomplete store with all the well-known issue
198 * status values. The store has some DIT-specific methods.
199 * TODO(jrobbins): would it be easier to define my own class to use
200 * instead of _AC_Simple_Store?
201 * @param {Array} openStatusDefs An array of definitions of the
202 * well-known open status values. Each definition has a name and
203 * docstring.
204 * @param {Array} closedStatusDefs An array of definitions of the
205 * well-known closed status values. Each definition has a name and
206 * docstring.
207 */
208 function TKR_setUpStatusStore(openStatusDefs, closedStatusDefs) {
209 var docdict = {};
210 TKR_statusWords = [];
211 for (var i = 0; i < openStatusDefs.length; i++) {
212 var status = openStatusDefs[i];
213 TKR_statusWords.push(status.name);
214 docdict[status.name] = status.doc;
215 }
216 for (var i = 0; i < closedStatusDefs.length; i++) {
217 var status = closedStatusDefs[i];
218 TKR_statusWords.push(status.name);
219 docdict[status.name] = status.doc;
220 }
221
222 TKR_statusStore = new _AC_SimpleStore(TKR_statusWords);
223 TKR_statusStore.docstrings = docdict;
224
225 TKR_statusStore.commaCompletes = false;
226
227 TKR_statusStore.substitute =
228 function(inputValue, cursor, completable, completion) {
229 return completion.value;
230 };
231
232 TKR_statusStore.completable = function(inputValue, cursor) {
233 if (!ac_everTyped) return '*status';
234 return inputValue;
235 }
236
237 TKR_statusStore.completions = function(prefix, tofilter) {
238 var fullList = TKR_openClosedComplete(prefix,
239 openStatusDefs,
240 closedStatusDefs);
241 if (fullList) return fullList;
242 return _AC_SimpleStore.prototype.completions.call(this, prefix, tofilter);
243 }
244
245 }
246
247
248 /**
249 * Simple function to add a given item to the list of items used to construct
250 * an "autocomplete store", and also update the docstring that describes
251 * that item. They are stored separately for backward compatability with
252 * autocomplete store logic that preceeded the introduction of descriptions.
253 */
254 function TKR_addACItem(items, docDict, item, docStr) {
255 items.push(item);
256 docDict[item] = docStr;
257 }
258
259 /**
260 * Add several autocomplete items to a word list that will be used to construct
261 * an autocomplete store. Also, keep track of description strings for each
262 * item. A search operator is prepended to the name of each item. The opt_old
263 * and opt_new parameters are used to transform Key-Value labels into Key=Value
264 * search terms.
265 */
266 function TKR_addACItemList(
267 items, docDict, searchOp, acDefs, opt_old, opt_new) {
268 var item;
269 for (var i = 0; i < acDefs.length; i++) {
270 var nameAndDoc = acDefs[i];
271 item = searchOp + nameAndDoc.name;
272 if (opt_old) {
273 // Preserve any leading minus-sign.
274 item = item.slice(0, 1) + item.slice(1).replace(opt_old, opt_new);
275 }
276 TKR_addACItem(items, docDict, item, nameAndDoc.doc)
277 }
278 }
279
280
281 /**
282 * Use information from an options feed to populate the artifact search
283 * autocomplete menu. The order of sections is: custom fields, labels,
284 * components, people, status, special, dates. Within each section,
285 * options are ordered semantically where possible, or alphabetically
286 * if there is no semantic ordering. Negated options all come after
287 * all normal options.
288 */
289 function TKR_setUpSearchStore(
290 labelDefs, memberDefs, openDefs, closedDefs, componentDefs, fieldDefs,
291 indMemberDefs) {
292 var searchWords = [];
293 var searchWordsNeg = [];
294 var docDict = {};
295
296 // Treat Key-Value and OneWord labels separately.
297 var keyValueLabelDefs = [];
298 var oneWordLabelDefs = [];
299 for (var i = 0; i < labelDefs.length; i++) {
300 var nameAndDoc = labelDefs[i];
301 if (nameAndDoc.name.indexOf('-') == -1) {
302 oneWordLabelDefs.push(nameAndDoc)
303 } else {
304 keyValueLabelDefs.push(nameAndDoc)
305 }
306 }
307
308 // Autocomplete for custom fields.
309 for (i = 0; i < fieldDefs.length; i++) {
310 var fieldName = fieldDefs[i]['field_name'];
311 var fieldType = fieldDefs[i]['field_type'];
312 if (fieldType == '1') { // enum type
313 var choices = fieldDefs[i]['choices'];
314 TKR_addACItemList(searchWords, docDict, fieldName + '=', choices);
315 TKR_addACItemList(searchWordsNeg, docDict, '-' + fieldName + '=', choices) ;
316 } else if (fieldType == '3') { // string types
317 TKR_addACItem(searchWords, docDict, fieldName + ':',
318 fieldDefs[i]['docstring']);
319 } else {
320 TKR_addACItem(searchWords, docDict, fieldName + '=',
321 fieldDefs[i]['docstring']);
322 }
323 TKR_addACItem(searchWords, docDict, 'has:' + fieldName,
324 'Issues with any ' + fieldName + ' value');
325 TKR_addACItem(searchWordsNeg, docDict, '-has:' + fieldName,
326 'Issues with no ' + fieldName + ' value');
327 }
328
329 // Add suggestions with "me" first, because otherwise they may be impossible
330 // to reach in a project that has a lot of members with emails starting with
331 // "me".
332 TKR_addACItem(searchWords, docDict, 'owner:me', 'Issues owned by me');
333 TKR_addACItem(searchWordsNeg, docDict, '-owner:me', 'Issues not owned by me');
334 TKR_addACItem(searchWords, docDict, 'cc:me', 'Issues that CC me');
335 TKR_addACItem(searchWordsNeg, docDict, '-cc:me', 'Issues that don\'t CC me');
336 TKR_addACItem(searchWords, docDict, 'reporter:me', 'Issues I reported');
337 TKR_addACItem(searchWordsNeg, docDict, '-reporter:me', 'Issues reported by oth ers');
338 TKR_addACItem(searchWords, docDict, 'commentby:me',
339 'Issues that I commented on');
340 TKR_addACItem(searchWordsNeg, docDict, '-commentby:me',
341 'Issues that I didn\'t comment on');
342
343 TKR_addACItemList(searchWords, docDict, '', keyValueLabelDefs, '-', '=');
344 TKR_addACItemList(searchWordsNeg, docDict, '-', keyValueLabelDefs, '-', '=');
345 TKR_addACItemList(searchWords, docDict, 'label:', oneWordLabelDefs);
346 TKR_addACItemList(searchWordsNeg, docDict, '-label:', oneWordLabelDefs);
347
348 TKR_addACItemList(searchWords, docDict, 'component:', componentDefs);
349 TKR_addACItemList(searchWordsNeg, docDict, '-component:', componentDefs);
350 TKR_addACItem(searchWords, docDict, 'has:component',
351 'Issues with any components specified');
352 TKR_addACItem(searchWordsNeg, docDict, '-has:component',
353 'Issues with no components specified');
354
355 TKR_addACItemList(searchWords, docDict, 'owner:', indMemberDefs);
356 TKR_addACItemList(searchWordsNeg, docDict, '-owner:', indMemberDefs);
357 TKR_addACItemList(searchWords, docDict, 'cc:', memberDefs);
358 TKR_addACItemList(searchWordsNeg, docDict, '-cc:', memberDefs);
359 TKR_addACItem(searchWords, docDict, 'has:cc',
360 'Issues with any cc\'d users');
361 TKR_addACItem(searchWordsNeg, docDict, '-has:cc',
362 'Issues with no cc\'d users');
363 TKR_addACItemList(searchWords, docDict, 'reporter:', memberDefs);
364 TKR_addACItemList(searchWordsNeg, docDict, '-reporter:', memberDefs);
365 TKR_addACItemList(searchWords, docDict, 'status:', openDefs);
366 TKR_addACItemList(searchWordsNeg, docDict, '-status:', openDefs);
367 TKR_addACItemList(searchWords, docDict, 'status:', closedDefs);
368 TKR_addACItemList(searchWordsNeg, docDict, '-status:', closedDefs);
369 TKR_addACItem(searchWords, docDict, 'has:status',
370 'Issues with any status');
371 TKR_addACItem(searchWordsNeg, docDict, '-has:status',
372 'Issues with no status');
373
374 TKR_addACItem(searchWords, docDict, 'is:blocked',
375 'Issues that are blocked');
376 TKR_addACItem(searchWordsNeg, docDict, '-is:blocked',
377 'Issues that are not blocked');
378 TKR_addACItem(searchWords, docDict, 'has:blockedon',
379 'Issues that are blocked');
380 TKR_addACItem(searchWordsNeg, docDict, '-has:blockedon',
381 'Issues that are not blocked');
382 TKR_addACItem(searchWords, docDict, 'has:blocking',
383 'Issues that are blocking other issues');
384 TKR_addACItem(searchWordsNeg, docDict, '-has:blocking',
385 'Issues that are not blocking other issues');
386
387 TKR_addACItem(searchWords, docDict, 'is:starred',
388 'Starred by me');
389 TKR_addACItem(searchWordsNeg, docDict, '-is:starred',
390 'Not starred by me');
391 TKR_addACItem(searchWords, docDict, 'stars>10',
392 'More than 10 stars');
393 TKR_addACItem(searchWords, docDict, 'stars>100',
394 'More than 100 stars');
395 TKR_addACItem(searchWords, docDict, 'summary:',
396 'Search within the summary field');
397
398 TKR_addACItemList(searchWords, docDict, 'commentby:', memberDefs);
399 TKR_addACItem(searchWords, docDict, 'attachment:',
400 'Search within attachment names');
401 TKR_addACItem(searchWords, docDict, 'attachments>5',
402 'Has more than 5 attachments');
403 TKR_addACItem(searchWords, docDict, 'is:open', 'Issues that are open');
404 TKR_addACItem(searchWordsNeg, docDict, '-is:open', 'Issues that are closed');
405 TKR_addACItem(searchWords, docDict, 'has:owner',
406 'Issues with some owner');
407 TKR_addACItem(searchWordsNeg, docDict, '-has:owner',
408 'Issues with no owner');
409 TKR_addACItem(searchWords, docDict, 'has:attachment',
410 'Issues with some attachments');
411 TKR_addACItem(searchWords, docDict, 'id:1,2,3',
412 'Match only the specified issues');
413 TKR_addACItem(searchWords, docDict, 'id<100000',
414 'Issues with IDs under 100,000');
415 TKR_addACItem(searchWords, docDict, 'blockedon:1',
416 'Blocked on the specified issues');
417 TKR_addACItem(searchWords, docDict, 'blocking:1',
418 'Blocking the specified issues');
419 TKR_addACItem(searchWords, docDict, 'is:spam', 'Issues classified as spam');
420 // We do not suggest -is:spam because it is implicit.
421
422 var today = new Date();
423 var todayStr = (today.getFullYear() + '/' + (today.getMonth() + 1) + '/' +
424 today.getDate());
425 TKR_addACItem(searchWords, docDict, 'opened>today-1',
426 'Opened within the last N days');
427 TKR_addACItem(searchWords, docDict, 'opened>' + todayStr,
428 'Opened after the specified date');
429 TKR_addACItem(searchWords, docDict, 'opened<today-1',
430 'Opened more than N days ago');
431 TKR_addACItem(searchWords, docDict, 'opened<' + todayStr,
432 'Opened before the specified date');
433 TKR_addACItem(searchWords, docDict, 'modified>today-1',
434 'Modified within the last N days');
435 TKR_addACItem(searchWords, docDict, 'modified>' + todayStr,
436 'Modified after the specified date');
437 TKR_addACItem(searchWords, docDict, 'modified<today-1',
438 'Modified more than N days ago');
439 TKR_addACItem(searchWords, docDict, 'modified<' + todayStr,
440 'Modified before the specified date');
441 TKR_addACItem(searchWords, docDict, 'closed>today-1',
442 'Closed within the last N days');
443 TKR_addACItem(searchWords, docDict, 'closed>' + todayStr,
444 'Closed after the specified date');
445 TKR_addACItem(searchWords, docDict, 'closed<today-1',
446 'Closed more than N days ago');
447 TKR_addACItem(searchWords, docDict, 'closed<' + todayStr,
448 'Closed before the specified date');
449
450 TKR_projectQueryStore = new _AC_SimpleStore(searchWords);
451 TKR_projectQueryStore.docstrings = docDict;
452
453 searchWords = searchWords.concat(searchWordsNeg);
454
455 TKR_searchStore = new _AC_SimpleStore(searchWords);
456 TKR_searchStore.docstrings = docDict;
457
458 // When we insert an autocomplete value, replace an entire search term.
459 // Add just a space after it (not a comma) if it is a complete search term,
460 // or leave the caret immediately after the completion if we are just helping
461 // the user with the search operator.
462 TKR_searchStore.substitute =
463 function(inputValue, caret, completable, completion) {
464 var nextTerm = caret;
465 while (inputValue.charAt(nextTerm) != ' ' &&
466 nextTerm < inputValue.length) {
467 nextTerm++;
468 }
469 while (inputValue.charAt(nextTerm) == ' ' &&
470 nextTerm < inputValue.length) {
471 nextTerm++;
472 }
473 return inputValue.substring(0, caret - completable.length) +
474 completion.value + ' ' + inputValue.substring(nextTerm);
475 };
476 TKR_searchStore.autoselectFirstRow =
477 function() {
478 return false;
479 };
480
481 TKR_projectQueryStore.substitute = TKR_searchStore.substitute;
482 TKR_projectQueryStore.autoselectFirstRow = TKR_searchStore.autoselectFirstRow;
483 }
484
485
486 /**
487 * Use information from an options feed to populate the issue quick edit
488 * autocomplete menu.
489 */
490 function TKR_setUpQuickEditStore(
491 labelDefs, memberDefs, openDefs, closedDefs, indMemberDefs) {
492 var qeWords = [];
493 var docDict = {};
494
495 // Treat Key-Value and OneWord labels separately.
496 var keyValueLabelDefs = [];
497 var oneWordLabelDefs = [];
498 for (var i = 0; i < labelDefs.length; i++) {
499 var nameAndDoc = labelDefs[i];
500 if (nameAndDoc.name.indexOf('-') == -1) {
501 oneWordLabelDefs.push(nameAndDoc)
502 } else {
503 keyValueLabelDefs.push(nameAndDoc)
504 }
505 }
506 TKR_addACItemList(qeWords, docDict, '', keyValueLabelDefs, '-', '=');
507 TKR_addACItemList(qeWords, docDict, '-', keyValueLabelDefs, '-', '=');
508 TKR_addACItemList(qeWords, docDict, '', oneWordLabelDefs);
509 TKR_addACItemList(qeWords, docDict, '-', oneWordLabelDefs);
510
511 TKR_addACItem(qeWords, docDict, 'owner=me', 'Make me the owner');
512 TKR_addACItem(qeWords, docDict, 'owner=----', 'Clear the owner field');
513 TKR_addACItem(qeWords, docDict, 'cc=me', 'CC me on this issue');
514 TKR_addACItem(qeWords, docDict, 'cc=-me', 'Remove me from CC list');
515 TKR_addACItemList(qeWords, docDict, 'owner=', indMemberDefs);
516 TKR_addACItemList(qeWords, docDict, 'cc=', memberDefs);
517 TKR_addACItemList(qeWords, docDict, 'cc=-', memberDefs);
518 TKR_addACItemList(qeWords, docDict, 'status=', openDefs);
519 TKR_addACItemList(qeWords, docDict, 'status=', closedDefs);
520 TKR_addACItem(qeWords, docDict, 'summary=""', 'Set the summary field');
521
522 TKR_quickEditStore = new _AC_SimpleStore(qeWords);
523 TKR_quickEditStore.docstrings = docDict;
524
525 // When we insert an autocomplete value, replace an entire command part.
526 // Add just a space after it (not a comma) if it is a complete part,
527 // or leave the caret immediately after the completion if we are just helping
528 // the user with the command operator.
529 TKR_quickEditStore.substitute =
530 function(inputValue, caret, completable, completion) {
531 var nextTerm = caret;
532 while (inputValue.charAt(nextTerm) != ' ' &&
533 nextTerm < inputValue.length) {
534 nextTerm++;
535 }
536 while (inputValue.charAt(nextTerm) == ' ' &&
537 nextTerm < inputValue.length) {
538 nextTerm++;
539 }
540 return inputValue.substring(0, caret - completable.length) +
541 completion.value + ' ' + inputValue.substring(nextTerm);
542 };
543 }
544
545
546
547 /**
548 * Constuct a new autocomplete store with all the project
549 * custom permissions.
550 * @param {Array} customPermissions An array of custom permission names.
551 */
552 function TKR_setUpCustomPermissionsStore(customPermissions) {
553 var permWords = ['View', 'EditIssue', 'AddIssueComment', 'DeleteIssue'];
554 var docdict = {
555 'View': '', 'EditIssue': '', 'AddIssueComment': '', 'DeleteIssue': ''};
556 for (var i = 0; i < customPermissions.length; i++) {
557 permWords.push(customPermissions[i]);
558 docdict[customPermissions[i]] = '';
559 }
560
561 TKR_customPermissionsStore = new _AC_SimpleStore(permWords);
562 TKR_customPermissionsStore.docstrings = docdict;
563
564 TKR_customPermissionsStore.commaCompletes = false;
565
566 TKR_customPermissionsStore.substitute =
567 function(inputValue, cursor, completable, completion) {
568 return completion.value;
569 };
570 }
571
572
573 /**
574 * Constuct a new autocomplete store with all the well-known project
575 * member user names and real names. The store has some
576 * monorail-specific methods.
577 * TODO(jrobbins): would it be easier to define my own class to use
578 * instead of _AC_Simple_Store?
579 * @param {Array} memberDefs An array of definitions of the project
580 * members. Each definition has a name and docstring.
581 */
582 function TKR_setUpMemberStore(memberDefs, indMemerDefs) {
583 var memberWords = [];
584 var indMemberWords = [];
585 var docdict = {};
586 for (var i = 0; i < memberDefs.length; i++) {
587 var member = memberDefs[i];
588 memberWords.push(member.name);
589 docdict[member.name] = member.doc;
590 if(!member.is_group) {
591 indMemberWords.push(member.name);
592 }
593 }
594
595 TKR_memberListStore = new _AC_SimpleStore(memberWords);
596 TKR_memberListStore.docstrings = docdict;
597
598 TKR_memberListStore.completions = function(prefix, tofilter) {
599 var fullList = TKR_fullComplete(prefix, memberDefs);
600 if (fullList) return fullList;
601 return _AC_SimpleStore.prototype.completions.call(this, prefix, tofilter);
602 }
603
604 TKR_memberListStore.completable = function(inputValue, cursor) {
605 if (inputValue == '') return '*member';
606 return _AC_SimpleStore.prototype.completable.call(this, inputValue, cursor);
607 }
608
609 TKR_memberListStore.substitute = TKR_acSubstituteWithComma;
610
611 TKR_ownerStore = new _AC_SimpleStore(indMemberWords);
612 TKR_ownerStore.docstrings = docdict;
613
614 TKR_ownerStore.commaCompletes = false;
615
616 TKR_ownerStore.substitute =
617 function(inputValue, cursor, completable, completion) {
618 return completion.value;
619 };
620
621 TKR_ownerStore.completions = function(prefix, tofilter) {
622 var fullList = TKR_fullComplete(prefix, indMemerDefs);
623 if (fullList) return fullList;
624 return _AC_SimpleStore.prototype.completions.call(this, prefix, tofilter);
625 };
626
627 TKR_ownerStore.completable = function(inputValue, cursor) {
628 if (!ac_everTyped) return '*owner';
629 return inputValue;
630 };
631
632 }
633
634
635 /**
636 * Constuct one new autocomplete store for each user-valued custom
637 * field that has a needs_perm validation requirement, and thus a
638 * list of allowed user indexes.
639 * TODO(jrobbins): would it be easier to define my own class to use
640 * instead of _AC_Simple_Store?
641 * @param {Array} fieldDefs An array of field definitions, only some
642 * of which have a 'user_indexes' entry.
643 * @param {Array} memberDefs An array of definitions of the project
644 * members. Each definition has a name and docstring.
645 */
646 function TKR_setUpUserAutocompleteStores(fieldDefs, memberDefs) {
647 for (var i = 0; i < fieldDefs.length; i++) {
648 var fieldDef = fieldDefs[i];
649 if (fieldDef['user_indexes']) {
650 var userIndexes = fieldDef['user_indexes'];
651 var qualifiedMembers = [];
652 for (var j = 0; j < userIndexes.length; j++) {
653 var mem = memberDefs[userIndexes[j]];
654 if (mem) qualifiedMembers.push(mem);
655 }
656 var us = makeOneUserAutocompleteStore(fieldDef, qualifiedMembers);
657 TKR_userAutocompleteStores['custom_' + fieldDef['field_id']] = us;
658 }
659 }
660 }
661
662 function makeOneUserAutocompleteStore(fieldDef, memberDefs) {
663 var memberWords = [];
664 var docdict = {};
665 for (var i = 0; i < memberDefs.length; i++) {
666 var member = memberDefs[i];
667 memberWords.push(member.name);
668 docdict[member.name] = member.doc;
669 }
670
671 var userStore = new _AC_SimpleStore(memberWords);
672 userStore.docstrings = docdict;
673 userStore.commaCompletes = false;
674
675 userStore.substitute =
676 function(inputValue, cursor, completable, completion) {
677 return completion.value;
678 };
679
680 userStore.completions = function(prefix, tofilter) {
681 var fullList = TKR_fullComplete(prefix, memberDefs);
682 if (fullList) return fullList;
683 return _AC_SimpleStore.prototype.completions.call(this, prefix, tofilter);
684 };
685
686 userStore.completable = function(inputValue, cursor) {
687 if (!ac_everTyped) return '*custom';
688 return inputValue;
689 };
690
691 return userStore;
692 }
693
694
695 /**
696 * Constuct a new autocomplete store with all the components.
697 * The store has some monorail-specific methods.
698 * @param {Array} componentDefs An array of definitions of components.
699 */
700 function TKR_setUpComponentStore(componentDefs) {
701 var componentWords = [];
702 var docdict = {};
703 for (var i = 0; i < componentDefs.length; i++) {
704 var component = componentDefs[i];
705 componentWords.push(component.name);
706 docdict[component.name] = component.doc;
707 }
708
709 TKR_componentListStore = new _AC_SimpleStore(componentWords);
710 TKR_componentListStore.docstrings = docdict;
711
712 TKR_componentListStore.completions = function(prefix, tofilter) {
713 var fullList = TKR_fullComplete(prefix, componentDefs);
714 if (fullList) return fullList;
715 return _AC_SimpleStore.prototype.completions.call(this, prefix, tofilter);
716 }
717
718 TKR_componentListStore.substitute = TKR_acSubstituteWithComma;
719
720 TKR_componentListStore.completable = function(inputValue, cursor) {
721 if (inputValue == '') return '*component';
722 return _AC_SimpleStore.prototype.completable.call(this, inputValue, cursor);
723 }
724
725 }
726
727
728 /**
729 * An array of definitions of all well-known issue labels. Each
730 * definition has the name of the label, and a docstring that
731 * describes its meaning.
732 */
733 var TKR_labelWords = [];
734
735
736 /**
737 * Constuct a new autocomplete store with all the well-known issue
738 * labels for the current project. The store has some DIT-specific methods.
739 * TODO(jrobbins): would it be easier to define my own class to use
740 * instead of _AC_Simple_Store?
741 * @param {Array} labelDefs An array of definitions of the project
742 * members. Each definition has a name and docstring.
743 */
744 function TKR_setUpLabelStore(labelDefs) {
745 TKR_labelWords = [];
746 var docdict = {};
747 for (var i = 0; i < labelDefs.length; i++) {
748 var label = labelDefs[i];
749 TKR_labelWords.push(label.name);
750 docdict[label.name] = label.doc;
751 }
752
753 TKR_labelStore = new _AC_SimpleStore(TKR_labelWords);
754 TKR_labelStore.docstrings = docdict;
755
756 TKR_labelStore.commaCompletes = false;
757 TKR_labelStore.substitute =
758 function(inputValue, cursor, completable, completion) {
759 return completion.value;
760 };
761
762 /* Given what the user typed, return the part of it that should be used
763 * to determine the auto-complete options offered to the user. */
764 TKR_labelStore.completable = function(inputValue, cursor) {
765 if (cursor == 0) {
766 return '*label'; // Show every well-known label that is not redundant.
767 }
768 var start = 0;
769 for (var i = cursor; --i >= 0;) {
770 var c = inputValue.charAt(i)
771 if (c == ' ' || c == ',') {
772 start = i + 1;
773 break;
774 }
775 }
776 var questionPos = inputValue.indexOf('?');
777 if (questionPos >= 0) {
778 // Ignore any "?" character and anything after it.
779 inputValue = inputValue.substring(start, questionPos);
780 }
781 var result = inputValue.substring(start, cursor);
782 if (inputValue.lastIndexOf('-') > 0 && !ac_everTyped) {
783 // Act like a menu: offer all alternative values for the same prefix.
784 result = inputValue.substring(
785 start, Math.min(cursor, inputValue.lastIndexOf('-')));
786 }
787 if (inputValue.startsWith('Restrict-') && !ac_everTyped) {
788 // If user is in the middle of 2nd part, use that to narrow the choices.
789 result = inputValue;
790 // If they completed 2nd part, give all choices matching 2-part prefix.
791 if (inputValue.lastIndexOf('-') > 8) {
792 result = inputValue.substring(
793 start, Math.min(cursor, inputValue.lastIndexOf('-') + 1));
794 }
795 }
796
797 return result;
798 };
799
800 /* Start with all labels or only those that match what the user typed so far,
801 * then filter out any that would lead to conflicts or redundancy. */
802 TKR_labelStore.completions = function(prefix, tofilter) {
803 var comps = TKR_fullComplete(prefix, labelDefs);
804 if (comps == null) {
805 comps = _AC_SimpleStore.prototype.completions.call(
806 this, prefix, tofilter);
807 }
808
809 var filtered_comps = [];
810 for (var i = 0; i < comps.length; i++) {
811 var prefix_parts = comps[i].value.split('-');
812 var label_prefix = prefix_parts[0].toLowerCase();
813 if (comps[i].value.startsWith('Restrict-')) {
814 if (!prefix.toLowerCase().startsWith('r')) {
815 // Consider restriction labels iff user has started typing.
816 continue;
817 }
818 if (prefix_parts.length > 1) {
819 label_prefix += '-' + prefix_parts[1].toLowerCase();
820 }
821 }
822 if (FindInArray(TKR_exclPrefixes, label_prefix) == -1 ||
823 TKR_usedPrefixes[label_prefix] == undefined ||
824 TKR_usedPrefixes[label_prefix].length == 0 ||
825 (TKR_usedPrefixes[label_prefix].length == 1 &&
826 TKR_usedPrefixes[label_prefix][0] == ac_focusedInput)) {
827 var uniq = true;
828 for (var p in TKR_usedPrefixes) {
829 var textFields = TKR_usedPrefixes[p];
830 for (var j = 0; j < textFields.length; j++) {
831 var tf = textFields[j];
832 if (tf.value.toLowerCase() == comps[i].value.toLowerCase() &&
833 tf != ac_focusedInput) {
834 uniq = false;
835 }
836 }
837 }
838 if (uniq) {
839 filtered_comps.push(comps[i]);
840 }
841 }
842 }
843
844 return filtered_comps;
845 };
846 }
847
848
849 /**
850 * Constuct a new autocomplete store with the given strings as choices.
851 * @param {Array} choices An array of autocomplete choices.
852 */
853 function TKR_setUpAutoCompleteStore(choices) {
854 TKR_autoCompleteStore = new _AC_SimpleStore(choices);
855 var choicesDefs = []
856 for (var i = 0; i < choices.length; ++i) {
857 choicesDefs.push({'name': choices[i], 'doc': ''});
858 }
859
860 /**
861 * Override the default completions() function to return a list of
862 * available choices. It proactively shows all choices when the user has
863 * not yet typed anything. It stops offering choices if the text field
864 * has a pretty long string in it already. It does not offer choices that
865 * have already been chosen.
866 */
867 TKR_autoCompleteStore.completions = function(prefix, tofilter) {
868 if (prefix.length > 18) {
869 return [];
870 }
871 var comps = TKR_fullComplete(prefix, choicesDefs);
872 if (comps == null) {
873 comps = _AC_SimpleStore.prototype.completions.call(
874 this, prefix, tofilter);
875 }
876
877 var usedComps = {}
878 var textFields = document.getElementsByTagName('input');
879 for (var i = 0; i < textFields.length; ++i) {
880 if (textFields[i].classList.contains('autocomplete')) {
881 usedComps[textFields[i].value] = true;
882 }
883 }
884 var unusedComps = []
885 for (i = 0; i < comps.length; ++i) {
886 if (!usedComps[comps[i].value]) {
887 unusedComps.push(comps[i]);
888 }
889 }
890
891 return unusedComps;
892 }
893
894 /**
895 * Override the default completable() function with one that gives a
896 * special value when the user has not yet typed anything. This
897 * causes TKR_fullComplete() to show all choices. Also, always consider
898 * the whole textfield value as an input to completion matching. Otherwise,
899 * it would only consider the part after the last comma (which makes sense
900 * for gmail To: and Cc: address fields).
901 */
902 TKR_autoCompleteStore.completable = function(inputValue, cursor) {
903 if (inputValue == '') {
904 return '*ac';
905 }
906 return inputValue;
907 }
908
909 /**
910 * Override the default substitute() function to completely replace the
911 * contents of the text field when the user selects a completion. Otherwise,
912 * it would append, much like the Gmail To: and Cc: fields append autocomplete
913 * selections.
914 */
915 TKR_autoCompleteStore.substitute =
916 function(inputValue, cursor, completable, completion) {
917 return completion.value;
918 };
919
920 /**
921 * We consider the whole textfield to be one value, not a comma separated
922 * list. So, typing a ',' should not trigger an autocomplete selection.
923 */
924 TKR_autoCompleteStore.commaCompletes = false;
925 }
926
927
928 /**
929 * XMLHTTP object used to fetch autocomplete options from the server.
930 */
931 var TKR_optionsXmlHttp = undefined;
932
933 /**
934 * URL used to fetch autocomplete options from the server, WITHOUT the
935 * project's cache content timestamp.
936 */
937 var TKR_optionsURL = undefined;
938
939 /**
940 * Contact the server to fetch the set of autocomplete options for the
941 * projects the user is contributor/member/owner of.
942 * If multiValue is set to true then the projectStore is configured to
943 * have support for multi-values (useful for example for saved queries where
944 * a query can apply to multiple projects).
945 */
946 function TKR_fetchUserProjects(multiValue) {
947 // Set a request token to prevent XSRF leaking of user project lists.
948 if (CS_env.token) {
949 var postURL = '/hosting/projects.do';
950 var xh = XH_XmlHttpCreate()
951 var data = 'token=' + CS_env.token;
952 var callback = multiValue ? TKR_fetchMultiValProjectsCallback
953 : TKR_fetchProjectsCallback;
954 XH_XmlHttpPOST(xh, postURL, data, callback);
955 }
956 }
957
958 /**
959 * Sets up the projectStore based on the json data received.
960 * The projectStore is setup with support for multiple values.
961 * @param {event} event with xhr Response with JSON data of projects.
962 */
963 function TKR_fetchMultiValProjectsCallback(event) {
964 var projects = TKR_getMemberProjects(event)
965 if (projects) {
966 TKR_setUpProjectStore(projects, true);
967 }
968 }
969
970 /**
971 * Sets up the projectStore based on the json data received.
972 * @param {event} event with xhr Response with JSON data of projects.
973 */
974 function TKR_fetchProjectsCallback(event) {
975 var projects = TKR_getMemberProjects(event)
976 if (projects) {
977 TKR_setUpProjectStore(projects, false);
978 }
979 }
980
981 function TKR_getMemberProjects(event) {
982 var xhr = event.target;
983 if (xhr) {
984 if (xhr.readyState != 4 || xhr.status != 200)
985 return;
986
987 var projects = [];
988 var json = CS_parseJSON(xhr);
989 for (var category in json) {
990 switch (category) {
991 case 'contributorto':
992 case 'memberof':
993 case 'ownerof':
994 for (var i = 0; i < json[category].length; i++) {
995 projects.push(json[category][i]);
996 }
997 break;
998 case 'error':
999 return;
1000 default:
1001 break;
1002 }
1003 }
1004 projects.sort();
1005 return projects;
1006 }
1007 }
1008
1009
1010 /**
1011 * Constuct a new autocomplete store with all the projects that the
1012 * current user has visibility into. The store has some monorail-specific
1013 * methods.
1014 * @param {Array} projects An array of project names.
1015 * @param {Boolean} multiValue Determines whether the store should support
1016 * multiple values.
1017 */
1018 function TKR_setUpProjectStore(projects, multiValue) {
1019 TKR_projectStore = new _AC_SimpleStore(projects);
1020 TKR_projectStore.commaCompletes = !multiValue;
1021
1022 var projectsDefs = []
1023 var docdict = {}
1024 for (var i = 0; i < projects.length; ++i) {
1025 projectsDefs.push({'name': projects[i], 'doc': ''});
1026 docdict[projects[i]] = '';
1027 }
1028
1029 TKR_projectStore.docstrings = docdict;
1030 if (multiValue) {
1031 TKR_projectStore.substitute = TKR_acSubstituteWithComma;
1032 } else {
1033 TKR_projectStore.substitute =
1034 function(inputValue, cursor, completable, completion) {
1035 return completion.value;
1036 };
1037 }
1038
1039 TKR_projectStore.completions = function(prefix, tofilter) {
1040 var fullList = TKR_fullComplete(prefix, projectsDefs);
1041 if (fullList) return fullList;
1042 return _AC_SimpleStore.prototype.completions.call(this, prefix, tofilter);
1043 };
1044
1045 TKR_projectStore.completable = function(inputValue, cursor) {
1046 if (inputValue == '') return '*project';
1047 if (multiValue)
1048 return _AC_SimpleStore.prototype.completable.call(
1049 this, inputValue, cursor);
1050 else
1051 return inputValue;
1052 };
1053 }
1054
1055
1056 /**
1057 * Contact the server to fetch the set of autocomplete options for the
1058 * current project. This is done with XMLHTTPRequest because the list
1059 * could be long, and most of the time, the user will only view an
1060 * issue not edit it.
1061 * @param {string} projectName The name of the current project.
1062 * @param {string} feedName The name of the feed to fetch.
1063 * @param {string} token The user's url-command-attack-prevention token.
1064 * @param {number} cct The project's cached-content-timestamp.
1065 * @param {Object} opt_args Key=value pairs.
1066 */
1067 function TKR_fetchOptions(projectName, feedName, token, cct, opt_args) {
1068 TKR_autoCompleteFeedName = feedName;
1069 TKR_optionsXmlHttp = XH_XmlHttpCreate();
1070 var projectPart = projectName ? '/p/' + projectName : '/hosting';
1071 TKR_optionsURL = (
1072 projectPart + '/feeds/' + feedName + '?' +
1073 'token=' + token);
1074 for (var arg in opt_args) {
1075 TKR_optionsURL += '&' + arg + '=' + encodeURIComponent(opt_args[arg]);
1076 }
1077
1078 XH_XmlHttpGET(
1079 TKR_optionsXmlHttp, TKR_optionsURL + '&cct=' + cct,
1080 TKR_issueOptionsFeedCallback);
1081 }
1082
1083
1084 /**
1085 * The communication with the server has made some progress. If it is
1086 * done, then process the response.
1087 */
1088 function TKR_issueOptionsFeedCallback() {
1089 if (TKR_optionsXmlHttp.readyState == 4) {
1090 if (TKR_optionsXmlHttp.status == 200) {
1091 TKR_gotIssueOptionsFeed(TKR_optionsXmlHttp);
1092 }
1093 }
1094 }
1095
1096
1097 /**
1098 * The server has sent the list of all options. Parse them and then set up each
1099 * of the label stores.
1100 * @param {Object} xhr The JSON response object the server. The response JSON
1101 * consists of one large dictionary with four items: open statuses,
1102 * closed statuses, issue labels, and project members.
1103 */
1104 function TKR_gotIssueOptionsFeed(xhr) {
1105 var json_data = null;
1106 try {
1107 json_data = CS_parseJSON(xhr);
1108 }
1109 catch (e) {
1110 return null;
1111 }
1112 indMemerDefs = []
1113 for (var i = 0; i < json_data.members.length; i++) {
1114 var member = json_data.members[i];
1115 if(!member.is_group) {
1116 indMemerDefs.push(member);
1117 }
1118 }
1119 TKR_setUpStatusStore(json_data.open, json_data.closed);
1120 TKR_setUpSearchStore(
1121 json_data.labels, json_data.members, json_data.open, json_data.closed,
1122 json_data.components, json_data.fields, indMemerDefs);
1123 TKR_setUpQuickEditStore(
1124 json_data.labels, json_data.members, json_data.open, json_data.closed,
1125 indMemerDefs);
1126 TKR_setUpLabelStore(json_data.labels);
1127 TKR_setUpComponentStore(json_data.components);
1128 TKR_setUpMemberStore(json_data.members, indMemerDefs);
1129 TKR_setUpUserAutocompleteStores(json_data.fields, json_data.members);
1130 TKR_setUpCustomPermissionsStore(json_data.custom_permissions);
1131 TKR_exclPrefixes = json_data.excl_prefixes;
1132 TKR_prepLabelAC(TKR_labelFieldIDPrefix);
1133 TKR_prepOwnerField(json_data.members);
1134 TKR_restrict_to_known = json_data.strict;
1135 }
OLDNEW
« no previous file with comments | « appengine/monorail/static/js/tracker/externs.js ('k') | appengine/monorail/static/js/tracker/tracker-components.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698