Add support for hashtags
Change-Id: Ib05f472be87b84a29157cfb158effb33c49ba458
This commit is contained in:
parent
4d4bed3711
commit
87d0f36c50
44
gertty/alembic/versions/399c4b3dcc9a_add_hashtags.py
Normal file
44
gertty/alembic/versions/399c4b3dcc9a_add_hashtags.py
Normal 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
|
@ -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)
|
||||
|
41
gertty/db.py
41
gertty/db.py
@ -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()
|
||||
|
||||
|
@ -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']],
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user