Add support for hashtags

Change-Id: Ib05f472be87b84a29157cfb158effb33c49ba458
This commit is contained in:
James E. Blair 2019-08-25 17:08:00 +02:00
parent 4d4bed3711
commit 87d0f36c50
6 changed files with 191 additions and 1 deletions

View File

@ -0,0 +1,44 @@
"""add_hashtags
Revision ID: 399c4b3dcc9a
Revises: 7ef7dfa2ca3a
Create Date: 2019-08-24 15:54:05.934760
"""
# revision identifiers, used by Alembic.
revision = '399c4b3dcc9a'
down_revision = '7ef7dfa2ca3a'
import warnings
from alembic import op
import sqlalchemy as sa
from gertty.dbsupport import sqlite_alter_columns
def upgrade():
op.create_table('hashtag',
sa.Column('key', sa.Integer(), nullable=False),
sa.Column('change_key', sa.Integer(), sa.ForeignKey('change.key'), index=True),
sa.Column('name', sa.String(length=255), index=True, nullable=False),
sa.PrimaryKeyConstraint('key')
)
with warnings.catch_warnings():
warnings.simplefilter("ignore")
op.add_column('change', sa.Column('pending_hashtags', sa.Boolean()))
connection = op.get_bind()
change = sa.sql.table('change',
sa.sql.column('pending_hashtags', sa.Boolean()))
connection.execute(change.update().values({'pending_hashtags':False}))
sqlite_alter_columns('change', [
sa.Column('pending_hashtags', sa.Boolean(), index=True, nullable=False),
])
def downgrade():
pass

View File

@ -802,6 +802,7 @@ class App(object):
message_keys = []
with self.db.getSession() as session:
account = session.getAccountByUsername(self.config.username)
account = session.getAccountByID(self.sync.account_id)
for revision_key in revision_keys:
k = self._saveReview(session, account, revision_key,
approvals, message, upload, submit)

View File

@ -80,6 +80,7 @@ change_table = Table(
Column('pending_topic', Boolean, index=True, nullable=False),
Column('pending_starred', Boolean, index=True, nullable=False),
Column('pending_status', Boolean, index=True, nullable=False),
Column('pending_hashtags', Boolean, index=True, nullable=False),
Column('pending_status_message', Text),
Column('last_seen', DateTime, index=True),
Column('outdated', Boolean, index=True, nullable=False),
@ -90,6 +91,12 @@ change_conflict_table = Table(
Column('change1_key', Integer, ForeignKey("change.key"), index=True),
Column('change2_key', Integer, ForeignKey("change.key"), index=True),
)
hashtag_table = Table(
'hashtag', metadata,
Column('key', Integer, primary_key=True),
Column('change_key', Integer, ForeignKey("change.key"), index=True),
Column('name', String(length=255), index=True, nullable=False),
)
revision_table = Table(
'revision', metadata,
Column('key', Integer, primary_key=True),
@ -237,6 +244,11 @@ class Project(object):
session.flush()
return b
class Hashtag(object):
def __init__(self, change, name):
self.change_key = change.key
self.name = name
class Branch(object):
def __init__(self, project, name):
self.project_key = project.key
@ -277,7 +289,8 @@ class Change(object):
hidden=False, reviewed=False, starred=False, held=False,
pending_rebase=False, pending_topic=False,
pending_starred=False, pending_status=False,
pending_status_message=None, outdated=False):
pending_status_message=None, pending_hashtags=False,
outdated=False):
self.project_key = project.key
self.account_key = owner.key
self.id = id
@ -295,6 +308,7 @@ class Change(object):
self.held = held
self.pending_rebase = pending_rebase
self.pending_topic = pending_topic
self.pending_hashtags = pending_hashtags
self.pending_starred = pending_starred
self.pending_status = pending_status
self.pending_status_message = pending_status_message
@ -382,6 +396,25 @@ class Change(object):
session.flush()
return l
def createHashtag(self, *args, **kw):
session = Session.object_session(self)
args = [self] + list(args)
h = Hashtag(*args, **kw)
self.hashtags.append(h)
session.add(h)
session.flush()
return h
def setHashtags(self, tags):
session = Session.object_session(self)
current_hashtags = [h.name for h in self.hashtags]
for hashtag in self.hashtags:
if hashtag.name not in tags:
session.delete(hashtag)
for hashtag in tags:
if hashtag not in current_hashtags:
self.createHashtag(hashtag)
@property
def owner_name(self):
owner_name = 'Anonymous Coward'
@ -660,6 +693,8 @@ mapper(Change, change_table, properties=dict(
primaryjoin=change_table.c.key==change_conflict_table.c.change2_key,
secondaryjoin=change_table.c.key==change_conflict_table.c.change1_key,
),
hashtags=relationship(Hashtag, backref='change',
cascade='all, delete-orphan'),
revisions=relationship(Revision, backref='change',
order_by=revision_table.c.number,
cascade='all, delete-orphan'),
@ -714,6 +749,7 @@ mapper(Approval, approval_table, properties=dict(
reviewer=relationship(Account)))
mapper(PendingCherryPick, pending_cherry_pick_table)
mapper(SyncQuery, sync_query_table)
mapper(Hashtag, hashtag_table)
def match(expr, item):
if item is None:
@ -981,6 +1017,9 @@ class DatabaseSession(object):
def getPendingTopics(self):
return self.session().query(Change).filter_by(pending_topic=True).all()
def getPendingHashtags(self):
return self.session().query(Change).filter_by(pending_hashtags=True).all()
def getPendingRebases(self):
return self.session().query(Change).filter_by(pending_rebase=True).all()

View File

@ -61,6 +61,7 @@ REBASE_CHANGE = 'rebase change'
CHERRY_PICK_CHANGE = 'cherry pick change'
REFRESH = 'refresh'
EDIT_TOPIC = 'edit topic'
EDIT_HASHTAGS = 'edit hashtags'
EDIT_COMMIT_MESSAGE = 'edit commit message'
SUBMIT_CHANGE = 'submit change'
SORT_BY_NUMBER = 'sort by number'
@ -129,6 +130,7 @@ DEFAULT_KEYMAP = {
CHERRY_PICK_CHANGE: 'ctrl x',
REFRESH: 'ctrl r',
EDIT_TOPIC: 'ctrl t',
EDIT_HASHTAGS: '#',
EDIT_COMMIT_MESSAGE: 'ctrl d',
SUBMIT_CHANGE: 'ctrl u',
SORT_BY_NUMBER: [['S', 'n']],

View File

@ -913,6 +913,9 @@ class SyncChangeTask(Task):
change.reviewed = False
result.review_flag_changed = True
app.project_cache.clear(change.project)
remote_hashtags = remote_change.get('hashtags', [])
change.setHashtags(remote_hashtags)
change.outdated = False
for url, refs in fetches.items():
self.log.debug("Fetching from %s with refs %s", url, refs)
@ -1012,6 +1015,8 @@ class UploadReviewsTask(Task):
with app.db.getSession() as session:
for c in session.getPendingTopics():
sync.submitTask(SetTopicTask(c.key, self.priority))
for c in session.getPendingHashtags():
sync.submitTask(SetHashtagsTask(c.key, self.priority))
for c in session.getPendingRebases():
sync.submitTask(RebaseChangeTask(c.key, self.priority))
for c in session.getPendingStatusChanges():
@ -1050,6 +1055,47 @@ class SetTopicTask(Task):
data)
sync.submitTask(SyncChangeTask(change.id, priority=self.priority))
class SetHashtagsTask(Task):
def __init__(self, change_key, priority=NORMAL_PRIORITY):
super(SetHashtagsTask, self).__init__(priority)
self.change_key = change_key
def __repr__(self):
return '<SetHashtagsTask %s>' % (self.change_key,)
def __eq__(self, other):
if (other.__class__ == self.__class__ and
other.change_key == self.change_key):
return True
return False
def run(self, sync):
app = sync.app
with app.db.getSession() as session:
change = session.getChange(self.change_key)
local_hashtags = [h.name for h in change.hashtags]
remote_change = sync.get('changes/%s' % change.id)
remote_hashtags = remote_change.get('hashtags', [])
with app.db.getSession() as session:
change = session.getChange(self.change_key)
remove = []
add = []
for hashtag in change.hashtags:
if hashtag.name not in remote_hashtags:
add.append(hashtag.name)
for hashtag in remote_hashtags:
if hashtag not in local_hashtags:
remove.append(hashtag)
data = dict(add=add, remove=remove)
change.pending_hashtags = False
# Inside db session for rollback
sync.post('changes/%s/hashtags' % (change.id,),
data)
sync.submitTask(SyncChangeTask(change.id, priority=self.priority))
class RebaseChangeTask(Task):
def __init__(self, change_key, priority=NORMAL_PRIORITY):
super(RebaseChangeTask, self).__init__(priority)

View File

@ -67,6 +67,34 @@ class EditTopicDialog(mywid.ButtonDialog):
return None
return key
class EditHashtagsDialog(mywid.ButtonDialog):
signals = ['save', 'cancel']
def __init__(self, app, hashtags):
self.app = app
save_button = mywid.FixedButton('Save')
cancel_button = mywid.FixedButton('Cancel')
urwid.connect_signal(save_button, 'click',
lambda button:self._emit('save'))
urwid.connect_signal(cancel_button, 'click',
lambda button:self._emit('cancel'))
super(EditHashtagsDialog, self).__init__("Edit Hashtags",
"Edit the change hashtags.",
entry_prompt="Hashtags: ",
entry_text=hashtags,
buttons=[save_button,
cancel_button],
ring=app.ring)
def keypress(self, size, key):
if not self.app.input_buffer:
key = super(EditHashtagsDialog, self).keypress(size, key)
keys = self.app.input_buffer + [key]
commands = self.app.config.keymap.getCommands(keys)
if keymap.ACTIVATE in commands:
self._emit('save')
return None
return key
class CherryPickDialog(urwid.WidgetWrap, mywid.LineBoxTitlePropertyMixin):
signals = ['save', 'cancel']
def __init__(self, app, change):
@ -522,6 +550,8 @@ class ChangeView(urwid.WidgetWrap):
"Refresh this change"),
(keymap.EDIT_TOPIC,
"Edit the topic of this change"),
(keymap.EDIT_HASHTAGS,
"Edit the hashtags of this change"),
(keymap.SUBMIT_CHANGE,
"Submit this change"),
(keymap.CHERRY_PICK_CHANGE,
@ -552,6 +582,7 @@ class ChangeView(urwid.WidgetWrap):
self.project_label = mywid.TextButton(u'', on_press=self.searchProject)
self.branch_label = urwid.Text(u'', wrap='clip')
self.topic_label = mywid.TextButton(u'', on_press=self.searchTopic)
self.hashtags_label = urwid.Text(u'', wrap='clip')
self.created_label = urwid.Text(u'', wrap='clip')
self.updated_label = urwid.Text(u'', wrap='clip')
self.status_label = urwid.Text(u'', wrap='clip')
@ -571,6 +602,7 @@ class ChangeView(urwid.WidgetWrap):
("Topic", urwid.Padding(urwid.AttrMap(self.topic_label, None,
focus_map=change_info_map),
width='pack')),
("Hashtags", self.hashtags_label),
("Created", self.created_label),
("Updated", self.updated_label),
("Status", self.status_label),
@ -657,6 +689,7 @@ class ChangeView(urwid.WidgetWrap):
change.last_seen = datetime.datetime.utcnow()
self.marked_seen = True
self.topic = change.topic or ''
self.hashtags = ', '.join([h.name for h in change.hashtags])
self.pending_status_message = change.pending_status_message or ''
reviewed = hidden = starred = held = ''
if change.reviewed:
@ -689,6 +722,7 @@ class ChangeView(urwid.WidgetWrap):
self.project_label.text.set_text(('change-data', change.project.name))
self.branch_label.set_text(('change-data', change.branch))
self.topic_label.text.set_text(('change-data', self.topic))
self.hashtags_label.set_text(('change-data', ' '.join([x.name for x in change.hashtags])))
self.created_label.set_text(('change-data', str(self.app.time(change.created))))
self.updated_label.set_text(('change-data', str(self.app.time(change.updated))))
self.status_label.set_text(('change-data', change.status))
@ -1005,6 +1039,9 @@ class ChangeView(urwid.WidgetWrap):
if keymap.EDIT_TOPIC in commands:
self.editTopic()
return None
if keymap.EDIT_HASHTAGS in commands:
self.editHashtags()
return None
if keymap.CHERRY_PICK_CHANGE in commands:
self.cherryPickChange()
return None
@ -1157,6 +1194,27 @@ class ChangeView(urwid.WidgetWrap):
self.app.backScreen()
self.refresh()
def editHashtags(self):
dialog = EditHashtagsDialog(self.app, self.hashtags)
urwid.connect_signal(dialog, 'save',
lambda button: self.closeEditHashtags(dialog, True))
urwid.connect_signal(dialog, 'cancel',
lambda button: self.closeEditHashtags(dialog, False))
self.app.popup(dialog)
def closeEditHashtags(self, dialog, save):
if save:
change_key = None
with self.app.db.getSession() as session:
change = session.getChange(self.change_key)
change.setHashtags([x.strip() for x in dialog.entry.edit_text.split(',')])
change.pending_hashtags = True
change_key = change.key
self.app.sync.submitTask(
sync.SetHashtagsTask(change_key, sync.HIGH_PRIORITY))
self.app.backScreen()
self.refresh()
def openPermalink(self, widget):
self.app.openURL(self.permalink_url)