Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Show a summarised snippet of the latest reply from each thread #214

Closed
wants to merge 7 commits into from
27 changes: 25 additions & 2 deletions securedrop_client/db.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# coding: utf-8

import os

from sqlalchemy import Boolean, Column, create_engine, DateTime, ForeignKey, Integer, String, \
Expand Down Expand Up @@ -26,9 +28,11 @@ def make_engine(home: str):

class WithContent():

data = None

@property
def content(self):
if self.is_downloaded:
if self.is_downloaded and self.data:
fn_no_ext, _ = os.path.splitext(self.filename)
return self.data.get(fn_no_ext)
else:
Expand Down Expand Up @@ -75,6 +79,26 @@ def collection(self):
collection.sort(key=lambda x: int(x.filename.split('-')[0]))
return collection

# XXX jt you are here, fix these.
@property
def last_activity_summary_text(self):
print("Last activity summary text!", self.collection)
if len(self.collection) == 0:
return ''

def ellipsis(content, n):
if len(content) <= n:
return content
else:
return '{}…'.format(content[:n])

last = self.collection[-1]
print("Last", last, "content", last.content)
prefix = '↳' if isinstance(last, Submission) else ''
content = last.content or ''

return '{}{}'.format(prefix, ellipsis(content, 100))


class Submission(Base, WithContent):
__tablename__ = 'submissions'
Expand All @@ -99,7 +123,6 @@ class Submission(Base, WithContent):

def __init__(self, source, uuid, size, filename, download_url):
# ORM event catching _should_ have already initialized `self.data`

self.source_id = source.id
self.uuid = uuid
self.size = size
Expand Down
6 changes: 6 additions & 0 deletions securedrop_client/gui/widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -265,10 +265,12 @@ def __init__(self, parent, source):
self.attached = load_svg('paperclip.svg')
self.attached.setMaximumSize(16, 16)
self.name = QLabel()
self.last_content = QLabel()
self.summary_layout.addWidget(self.name)
self.summary_layout.addStretch()
self.summary_layout.addWidget(self.attached)
layout.addWidget(self.summary)
layout.addWidget(self.last_content)
self.updated = QLabel()
layout.addWidget(self.updated)
self.delete = load_svg('cross.svg')
Expand Down Expand Up @@ -311,6 +313,10 @@ def update(self):
if self.source.document_count == 0:
self.attached.hide()

self.last_content.setText(
"{}".format(html.escape(self.source.last_activity_summary_text))
)

def toggle_star(self, event):
"""
Called when the star is clicked.
Expand Down
9 changes: 7 additions & 2 deletions securedrop_client/logic.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,19 @@ def setup(self):
# that as downloads/decryption occur, the messages and replies
# populate the view.
self.conv_view_update = QTimer()
self.conv_view_update.timeout.connect(
self.update_conversation_view)
self.conv_view_update.timeout.connect(self.update_views)

self.conv_view_update.start(1000 * 60 * 0.10) # every 6 seconds

event.listen(db.Submission, 'load', self.on_object_loaded)
event.listen(db.Submission, 'init', self.on_object_instantiated)
event.listen(db.Reply, 'load', self.on_object_loaded)
event.listen(db.Reply, 'init', self.on_object_instantiated)

def update_views(self):
self.update_sources()
self.update_conversation_view()

def on_object_instantiated(self, target, args, kwargs):
target.data = Data(self.data_dir)
return target
Expand Down Expand Up @@ -581,6 +585,7 @@ def on_file_downloaded(self, result, current_object):
# Refresh the current source conversation, bearing in mind
# that the user may have navigated to another source.
self.gui.show_conversation_for(self.gui.current_source)
# self.update_sources()
self.set_status(
'Finished downloading {}'.format(current_object.filename))
else: # The file did not download properly.
Expand Down
21 changes: 10 additions & 11 deletions tests/gui/test_widgets.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,19 +153,18 @@ def test_SourceList_update(mocker):
sl.addItem = mocker.MagicMock()
sl.setItemWidget = mocker.MagicMock()
sl.controller = mocker.MagicMock()

mock_sw = mocker.MagicMock()
mock_lwi = mocker.MagicMock()
mocker.patch('securedrop_client.gui.widgets.SourceWidget', mock_sw)
mocker.patch('securedrop_client.gui.widgets.QListWidgetItem', mock_lwi)

sources = ['a', 'b', 'c', ]
sl.update(sources)
sl.clear.assert_called_once_with()
assert mock_sw.call_count == len(sources)
assert mock_lwi.call_count == len(sources)
assert sl.addItem.call_count == len(sources)
assert sl.setItemWidget.call_count == len(sources)
with mocker.patch('securedrop_client.gui.widgets.SourceWidget', mock_sw), \
mocker.patch('securedrop_client.gui.widgets.QListWidgetItem',
mock_lwi):
sources = ['a', 'b', 'c', ]
sl.update(sources)
sl.clear.assert_called_once_with()
assert mock_sw.call_count == len(sources)
assert mock_lwi.call_count == len(sources)
assert sl.addItem.call_count == len(sources)
assert sl.setItemWidget.call_count == len(sources)


def test_SourceList_maintains_selection(mocker):
Expand Down
22 changes: 22 additions & 0 deletions tests/test_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,25 @@ def test_source_collection():
# Now these items should be in the source collection in the proper order
assert source.collection[0] == reply
assert source.collection[1] == submission


def test_last_activity_summary_text(mocker):
source = factory.Source()
submission = Submission(source=source, uuid="test", size=123,
filename="2-test.doc.gpg",
download_url='http://test/test')
user = User('hehe')
reply = Reply(source=source, journalist=user, filename="1-reply.gpg",
size=1234, uuid='test')

reply_with_content = mocker.MagicMock(wrap=reply, content='reply content')
submission_content = mocker.MagicMock(wrap=submission, content='submission content')

source.submissions = [reply_with_content]
source.replies = [submission_content]

assert source.last_activity_summary_text == 'submission content'

submission_content.content = 'extremely long content' * 50
assert source.last_activity_summary_text[-1] == '…'
assert len(source.last_activity_summary_text) < len(submission_content.content)