From 64cfe757676e8a7d21690def85e3ea53eb150426 Mon Sep 17 00:00:00 2001
From: Bradley Jones <jones.bradley@me.com>
Date: Thu, 18 Sep 2014 11:30:15 +0100
Subject: [PATCH] Add ability to sort change list

Added key bindings to sort the change list by number (current default), and by
the time the change was last updated. Also added the ability to reverse the
sort, useful for showing the most recent changes at the top of the screen.

Doesn't depend on but sorting by updated column makes more sense with this
patchset: https://review.openstack.org/#/c/122176/

Change-Id: I4d0ee462c106e26857785c9b4e26cc17e906796a
---
 gertty/db.py               |  7 ++++--
 gertty/keymap.py           |  6 +++++
 gertty/view/change_list.py | 48 +++++++++++++++++++++++++++++++++++---
 3 files changed, 56 insertions(+), 5 deletions(-)

diff --git a/gertty/db.py b/gertty/db.py
index 2cf23ae..381a59f 100644
--- a/gertty/db.py
+++ b/gertty/db.py
@@ -578,12 +578,15 @@ class DatabaseSession(object):
         except sqlalchemy.orm.exc.NoResultFound:
             return None
 
-    def getChanges(self, query, unreviewed=False):
+    def getChanges(self, query, unreviewed=False, sort_by='number'):
         self.database.log.debug("Search query: %s" % query)
         q = self.session().query(Change).filter(self.search.parse(query))
         if unreviewed:
             q = q.filter(change_table.c.hidden==False, change_table.c.reviewed==False)
-        q = q.order_by(change_table.c.number)
+        if sort_by == 'updated':
+            q = q.order_by(change_table.c.updated)
+        else:
+            q = q.order_by(change_table.c.number)
         self.database.log.debug("Search SQL: %s" % q)
         try:
             return q.all()
diff --git a/gertty/keymap.py b/gertty/keymap.py
index afb5189..17fb200 100644
--- a/gertty/keymap.py
+++ b/gertty/keymap.py
@@ -53,6 +53,9 @@ REFRESH = 'refresh'
 EDIT_TOPIC = 'edit topic'
 EDIT_COMMIT_MESSAGE = 'edit commit message'
 SUBMIT_CHANGE = 'submit change'
+SORT_BY_NUMBER = 'sort by number'
+SORT_BY_UPDATED = 'sort by updated'
+SORT_BY_REVERSE = 'reverse the sort'
 # Project list screen:
 TOGGLE_LIST_REVIEWED = 'toggle list reviewed'
 TOGGLE_LIST_SUBSCRIBED = 'toggle list subscribed'
@@ -97,6 +100,9 @@ DEFAULT_KEYMAP = {
     EDIT_TOPIC: 'ctrl t',
     EDIT_COMMIT_MESSAGE: 'ctrl d',
     SUBMIT_CHANGE: 'ctrl u',
+    SORT_BY_NUMBER: 'n',
+    SORT_BY_UPDATED: 'u',
+    SORT_BY_REVERSE: 'r',
 
     TOGGLE_LIST_REVIEWED: 'l',
     TOGGLE_LIST_SUBSCRIBED: 'L',
diff --git a/gertty/view/change_list.py b/gertty/view/change_list.py
index 58df858..fb39f74 100644
--- a/gertty/view/change_list.py
+++ b/gertty/view/change_list.py
@@ -106,7 +106,13 @@ class ChangeListView(urwid.WidgetWrap):
             (key(keymap.TOGGLE_REVIEWED),
              "Toggle the reviewed flag for the currently selected change"),
             (key(keymap.REFRESH),
-             "Sync all projects")
+             "Sync all projects"),
+            (key(keymap.SORT_BY_NUMBER),
+             "Sort changes by number"),
+            (key(keymap.SORT_BY_UPDATED),
+             "Sort changes by how recently the change was updated"),
+            (key(keymap.SORT_BY_REVERSE),
+             "Reverse the sort")
             ]
 
     def __init__(self, app, query, query_desc=None, unreviewed=False):
@@ -120,6 +126,8 @@ class ChangeListView(urwid.WidgetWrap):
         self.display_owner = self.display_project = self.display_updated = True
         if '_project_key' in query:
             self.display_project = False
+        self.sort_by = 'number'
+        self.reverse = False
         self.header = ChangeListHeader(self.display_project, self.display_owner,
                                        self.display_updated)
         self.categories = []
@@ -133,7 +141,8 @@ class ChangeListView(urwid.WidgetWrap):
     def refresh(self):
         unseen_keys = set(self.change_rows.keys())
         with self.app.db.getSession() as session:
-            lst = session.getChanges(self.query, self.unreviewed)
+            lst = session.getChanges(self.query, self.unreviewed,
+                                     sort_by=self.sort_by)
             if self.unreviewed:
                 self.title = u'Unreviewed changes in %s' % self.query_desc
             else:
@@ -144,7 +153,11 @@ class ChangeListView(urwid.WidgetWrap):
                 categories |= set(change.getCategories())
             self.categories = sorted(categories)
             i = 0
-            for change in lst:
+            if self.reverse:
+                change_list = reversed(lst)
+            else:
+                change_list = lst
+            for change in change_list:
                 row = self.change_rows.get(change.key)
                 if not row:
                     row = ChangeRow(change, self.categories, self.display_project,
@@ -163,6 +176,11 @@ class ChangeListView(urwid.WidgetWrap):
             self.listbox.body.remove(row)
             del self.change_rows[key]
 
+    def clearChangeList(self):
+        for key, value in self.change_rows.iteritems():
+            self.listbox.body.remove(value)
+        self.change_rows = {}
+
     def getNextChangeKey(self, change_key):
         row = self.change_rows.get(change_key)
         try:
@@ -225,6 +243,30 @@ class ChangeListView(urwid.WidgetWrap):
                 sync.SyncSubscribedProjectsTask(sync.HIGH_PRIORITY))
             self.app.status.update()
             return None
+        if keymap.SORT_BY_NUMBER in commands:
+            if not len(self.listbox.body):
+                return None
+            self.sort_by = 'number'
+            self.clearChangeList()
+            self.refresh()
+            return None
+        if keymap.SORT_BY_UPDATED in commands:
+            if not len(self.listbox.body):
+                return None
+            self.sort_by = 'updated'
+            self.clearChangeList()
+            self.refresh()
+            return None
+        if keymap.SORT_BY_REVERSE in commands:
+            if not len(self.listbox.body):
+                return None
+            if self.reverse:
+                self.reverse = False
+            else:
+                self.reverse = True
+            self.clearChangeList()
+            self.refresh()
+            return None
         return key
 
     def onSelect(self, button, change_key):