Implement search-as-you-type for assignees

This commit is contained in:
Ivan Habunek 2018-12-03 16:29:40 +01:00 committed by Drew DeVault
parent a80b309734
commit 3f955def1a
3 changed files with 117 additions and 0 deletions

View File

@ -5,6 +5,7 @@ from srht.database import db
from srht.flask import loginrequired
from srht.validation import Validation
from todosrht.access import get_tracker, get_ticket
from todosrht.search import find_usernames
from todosrht.tickets import add_comment, mark_seen, assign, unassign
from todosrht.types import Event, EventType
from todosrht.types import Label, TicketLabel
@ -331,3 +332,11 @@ def ticket_unassign(owner, name, ticket_id):
db.session.commit()
return redirect(ticket_url(ticket))
@ticket.route("/usernames")
def usernames():
query = request.args.get('q')
return {
"results": find_usernames(query)
}

View File

@ -1,5 +1,6 @@
import re
from sqlalchemy import or_
from todosrht.app import db
from todosrht.types import Label, TicketLabel
from todosrht.types import Ticket, TicketStatus, TicketComment
from todosrht.types import User
@ -106,3 +107,21 @@ def apply_search(query, search, tracker, current_user):
Ticket.comments.any(TicketComment.text.ilike("%" + value + "%"))))
return query
def find_usernames(query, limit=20):
"""Given a partial username string, returns matching usernames."""
if not query or query == '~':
return []
if query.startswith("~"):
where = User.username.startswith(query[1:], autoescape=True)
else:
where = User.username.contains(query, autoescape=True)
rows = (db.session
.query(User.username)
.filter(where)
.order_by(User.username)
.limit(limit))
return [f"~{r[0]}" for r in rows]

View File

@ -146,10 +146,14 @@
{{ csrf_token() }}
<div class="form-group">
<input
id="assignee-input"
type="text"
name="username"
autocomplete="off"
list="assignee-list"
class="form-control {{valid.cls("username")}}"
value="{{username}}" />
<datalist id="assignee-list"></datalist>
{{valid.summary("username")}}
</div>
<div class="pull-right">
@ -337,3 +341,88 @@
</div>
</div>
{% endblock %}
{% block scripts %}
<script type="text/javascript">
function UserAutoComplete(input, list) {
this.input = input;
this.list = list;
this.lastQuery = input.value;
// Settings
this.delay = 250;
this.minQueryLength = 3;
this.onLoad = function(event) {
const response = event.target;
if (response.status == 200) {
const data = JSON.parse(response.responseText);
this.list.innerHTML = '';
data.results.forEach(function (username) {
const option = document.createElement('option');
option.value = username;
this.list.appendChild(option);
}.bind(this));
}
}.bind(this)
this.sendRequest = function(query) {
const search = encodeURIComponent(query);
const request = new XMLHttpRequest();
request.onload = this.onLoad;
request.open("GET", "/usernames/?q=" + search);
request.send();
}
this.search = function() {
const query = this.input.value
if (query == "" || query == "~") {
this.list.innerHTML = "";
this.lastQuery = "";
return;
}
const notRepeated = query !== this.lastQuery;
const notTooShort = query.length >= this.minQueryLength;
if (notRepeated && notTooShort) {
this.sendRequest(query);
this.lastQuery = query;
}
}.bind(this)
this.debounce = function(fn, delay) {
let timeout = null;
return function() {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(fn, delay);
}
}
this.register = function() {
this.input.addEventListener("input",
this.debounce(this.search, this.delay));
// Prevent search being triggered when an user is selected from the datalist
// 'select' works in Firefox, 'change' works in Chrome
this.input.addEventListener("select", function(e) {
this.lastQuery = e.target.value;
}.bind(this));
this.input.addEventListener("change", function(e) {
this.lastQuery = e.target.value;
}.bind(this));
}
}
(function() {
const input = document.getElementById("assignee-input");
const list = document.getElementById("assignee-list");
autocomplete = new UserAutoComplete(input, list);
autocomplete.register();
})();
</script>
{% endblock %}