~sircmpwn/sr.ht-dev

Development discussion for sr.ht. When contributing patches to sr.ht, please edit the [PATCH] line to include the specific sr.ht project you're contributing to, e.g.

[PATCH lists.sr.ht v2] Add thing to stuff
4 2

[PATCH todo.sr.ht 0/3] Reuse username autocomplete

Details
Message ID
<20190811121203.11370-1-ivan@habunek.com>
DKIM signature
pass
Download raw message
Extract relevant code and reuse it on the tracker access page.

Ivan Habunek (3):
  Extract user autocomplete to enable reuse
  Extract getting recent users
  Add username completion to tracker access page

 todosrht/blueprints/ticket.py          | 16 ++----
 todosrht/blueprints/tracker.py         |  8 +--
 todosrht/templates/autocomplete.js     | 72 ++++++++++++++++++++++++++
 todosrht/templates/ticket.html         | 72 +-------------------------
 todosrht/templates/tracker-access.html | 22 ++++++++
 todosrht/trackers.py                   | 15 ++++++
 6 files changed, 119 insertions(+), 86 deletions(-)
 create mode 100644 todosrht/templates/autocomplete.js
 create mode 100644 todosrht/trackers.py

-- 
2.20.1

[PATCH todo.sr.ht 1/3] Extract user autocomplete to enable reuse

Details
Message ID
<20190811121203.11370-2-ivan@habunek.com>
In-Reply-To
<20190811121203.11370-1-ivan@habunek.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +73 -71
---
 todosrht/templates/autocomplete.js | 72 ++++++++++++++++++++++++++++++
 todosrht/templates/ticket.html     | 72 +-----------------------------
 2 files changed, 73 insertions(+), 71 deletions(-)
 create mode 100644 todosrht/templates/autocomplete.js

diff --git a/todosrht/templates/autocomplete.js b/todosrht/templates/autocomplete.js
new file mode 100644
index 0000000..f16364b
--- /dev/null
+++ b/todosrht/templates/autocomplete.js
@@ -0,0 +1,72 @@
+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));
+  }
+}
diff --git a/todosrht/templates/ticket.html b/todosrht/templates/ticket.html
index 4abf522..0efdf36 100644
--- a/todosrht/templates/ticket.html
+++ b/todosrht/templates/ticket.html
@@ -395,78 +395,8 @@
 
 {% 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));
-  }
-}
+{% include "autocomplete.js" %}
 
 (function() {
   const input = document.getElementById("assignee-input");
-- 
2.20.1

[PATCH todo.sr.ht 2/3] Extract getting recent users

Details
Message ID
<20190811121203.11370-3-ivan@habunek.com>
In-Reply-To
<20190811121203.11370-1-ivan@habunek.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +19 -12
---
 todosrht/blueprints/ticket.py | 16 ++++------------
 todosrht/trackers.py          | 15 +++++++++++++++
 2 files changed, 19 insertions(+), 12 deletions(-)
 create mode 100644 todosrht/trackers.py

diff --git a/todosrht/blueprints/ticket.py b/todosrht/blueprints/ticket.py
index 2e4f6b5..4e9ea71 100644
--- a/todosrht/blueprints/ticket.py
+++ b/todosrht/blueprints/ticket.py
@@ -8,13 +8,14 @@ from todosrht.access import get_tracker, get_ticket
 from todosrht.filters import invalidate_markup_cache
 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, Ticket, TicketLabel
+from todosrht.trackers import get_recent_users
+from todosrht.types import Event, EventType, Label, TicketLabel
 from todosrht.types import TicketAccess, TicketResolution
 from todosrht.types import TicketSubscription, User
 from todosrht.urls import ticket_url
 from todosrht.webhooks import TrackerWebhook, TicketWebhook
 
+
 ticket = Blueprint("ticket", __name__)
 
 def get_ticket_context(ticket, tracker, access):
@@ -28,15 +29,6 @@ def get_ticket_context(ticket, tracker, access):
         ticket_sub = TicketSubscription.query.filter_by(
             ticket=ticket, tracker=None, user=current_user).one_or_none()
 
-    # Find users who recently interacted with this tracker
-    recent_user_events = (db.session.query(Event.id, User.username)
-        .join(User, User.id == Event.user_id)
-        .join(Ticket, Ticket.id == Event.ticket_id)
-        .filter(Ticket.tracker_id == tracker.id)
-        .order_by(Event.created.desc())
-        .limit(20))
-    recent_users = {e[1] for e in recent_user_events}
-
     return {
         "tracker": tracker,
         "ticket": ticket,
@@ -46,7 +38,7 @@ def get_ticket_context(ticket, tracker, access):
         "access": access,
         "tracker_sub": tracker_sub,
         "ticket_sub": ticket_sub,
-        "recent_users": recent_users,
+        "recent_users": get_recent_users(tracker),
     }
 
 @ticket.route("/<owner>/<name>/<int:ticket_id>")
diff --git a/todosrht/trackers.py b/todosrht/trackers.py
new file mode 100644
index 0000000..5ebdf60
--- /dev/null
+++ b/todosrht/trackers.py
@@ -0,0 +1,15 @@
+from srht.database import db
+from todosrht.types import Event, Ticket, User
+
+
+def get_recent_users(tracker, limit=20):
+    """Find users who recently interacted with a tracker."""
+
+    recent_user_events = (db.session.query(Event.id, User.username)
+        .join(User, User.id == Event.user_id)
+        .join(Ticket, Ticket.id == Event.ticket_id)
+        .filter(Ticket.tracker_id == tracker.id)
+        .order_by(Event.created.desc())
+        .limit(20))
+
+    return {e[1] for e in recent_user_events}
-- 
2.20.1

[PATCH todo.sr.ht 3/3] Add username completion to tracker access page

Details
Message ID
<20190811121203.11370-4-ivan@habunek.com>
In-Reply-To
<20190811121203.11370-1-ivan@habunek.com> (view parent)
DKIM signature
pass
Download raw message
Patch: +27 -3
---
 todosrht/blueprints/tracker.py         |  8 +++++---
 todosrht/templates/tracker-access.html | 22 ++++++++++++++++++++++
 2 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/todosrht/blueprints/tracker.py b/todosrht/blueprints/tracker.py
index bd65d8c..cb9ce97 100644
--- a/todosrht/blueprints/tracker.py
+++ b/todosrht/blueprints/tracker.py
@@ -5,10 +5,11 @@ from todosrht.access import get_tracker
 from todosrht.search import apply_search
 from todosrht.tickets import get_last_seen_times, get_comment_counts
 from todosrht.tickets import submit_ticket
-from todosrht.types import TicketSubscription, User
+from todosrht.trackers import get_recent_users
 from todosrht.types import Event, UserAccess
-from todosrht.types import Tracker, Ticket, TicketAccess
 from todosrht.types import Label, TicketLabel
+from todosrht.types import TicketSubscription, User
+from todosrht.types import Tracker, Ticket, TicketAccess
 from todosrht.urls import tracker_url, ticket_url
 from todosrht.webhooks import TrackerWebhook, UserWebhook
 from srht.config import cfg
@@ -209,9 +210,10 @@ def settings_details_POST(owner, name):
 
 
 def render_tracker_access(tracker, **kwargs):
+    recent_users = get_recent_users(tracker)
     return render_template("tracker-access.html",
         view="access", tracker=tracker, access_type_list=TicketAccess,
-        access_help_map=access_help_map, **kwargs)
+        access_help_map=access_help_map, recent_users=recent_users, **kwargs)
 
 
 @tracker.route("/<owner>/<name>/settings/access")
diff --git a/todosrht/templates/tracker-access.html b/todosrht/templates/tracker-access.html
index 01a0f6c..19cd1ad 100644
--- a/todosrht/templates/tracker-access.html
+++ b/todosrht/templates/tracker-access.html
@@ -109,8 +109,15 @@
           id="username"
           name="username"
           placeholder="~{{ current_user.username }}"
+          autocomplete="off"
+          list="user-list"
           value="{{username or ""}}"
         />
+        <datalist id="user-list">
+          {% for u in recent_users %}
+            <option value="~{{ u }}" />
+          {% endfor %}
+        </datalist>
         {{ valid.summary("username") }}
       </div>
 
@@ -182,3 +189,18 @@
   </div>
 </div>
 {% endblock %}
+
+{% block scripts %}
+<script type="text/javascript">
+  {% include "autocomplete.js" %}
+
+  (function() {
+    const input = document.getElementById("username");
+    const list = document.getElementById("user-list");
+    if (input && list) {
+      autocomplete = new UserAutoComplete(input, list);
+      autocomplete.register();
+    }
+  })();
+</script>
+{% endblock %}
-- 
2.20.1
Details
Message ID
<BW79K0MUOZBM.12J31U2UDG8YX@koishi>
In-Reply-To
<20190811121203.11370-1-ivan@habunek.com> (view parent)
DKIM signature
pass
Download raw message
Thanks!

To git.sr.ht:~sircmpwn/todo.sr.ht
   c8ef2f9..e7aab9c  master -> master
 * [new tag]         0.46.7 -> 0.46.7