Skip to content

Commit

Permalink
Merge pull request #1582 from freedomofpress/add-conversation-digest
Browse files Browse the repository at this point in the history
Add conversation.Transcript
  • Loading branch information
cfm authored Jan 5, 2023
2 parents 82032c2 + 61226ad commit 95ca10a
Show file tree
Hide file tree
Showing 19 changed files with 289 additions and 0 deletions.
1 change: 1 addition & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ $(POT): securedrop_client
@echo "updating catalog template: $@"
@mkdir -p ${LOCALE_DIR}
@pybabel extract \
-F babel.cfg \
--charset=utf-8 \
--output=${POT} \
--project="SecureDrop Client" \
Expand Down
8 changes: 8 additions & 0 deletions babel.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Extract from Python source files and Jinja templates.

[python: **.py]

[jinja2: **.jinja]
# Warn on Jinja syntax errors
# (https://jinja.palletsprojects.com/en/3.0.x/integration/#babel).
silent=False
1 change: 1 addition & 0 deletions requirements/build-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ arrow==0.12.1 --hash=sha256:e2742eb33011f7aff1d5f27501d802680b6627939f20ff2ef56f
certifi==2022.12.7 --hash=sha256:7f205a1a4f02f4970fb5d0e16457964bb30d6b678a766515278bc56e6eeb645f
charset-normalizer==2.0.4 --hash=sha256:cd9a4492eef4e5276c07f9c0dc1338e7be3e95f2a536bf2c5b620b1f27d03d74
idna==3.2 --hash=sha256:691d9fc304505c65ea9ceb8eb7385d63988e344c065cacbbd2156ff9bdfcf0c1
jinja2==3.0.2 --hash=sha256:d8075dbbb594058c565a74b6ca2b6a1822c9cdd949400b747f87ec004edca036
mako==1.2.2 --hash=sha256:f61384bcc80318821d1116891a82bb0ff18a9a4035c7c4eff72aced45ab590b5
markupsafe==2.0.1 --hash=sha256:bb3e541812095075336bcd935bb58941aedc0a7cba3c73d301dfdfd4d66a4eec --hash=sha256:7b12b29ae39060c29ed0d8cb1052fa1672832b5096f859fd35e896ca3b04ddd3 --hash=sha256:9a055a175f351a559937fb80ebb2885d005283577a016c0139817e261fb759eb
pathlib2==2.3.2 --hash=sha256:90173e12465846173da76c62892b238c14a2a0e17aae580933041004fc01b713
Expand Down
5 changes: 5 additions & 0 deletions requirements/dev-bookworm-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ isort==5.10.1 \
--hash=sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7 \
--hash=sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951
# via -r requirements/dev-sdw-requirements.in
jinja2==3.0.2 \
--hash=sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45 \
--hash=sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c
# via -r requirements/requirements.in
jsonschema==4.17.0 \
--hash=sha256:5bfcf2bca16a087ade17e02b282d34af7ccd749ef76241e7f9bd7c0cb8a9424d \
--hash=sha256:f660066c3966db7d6daeaea8a75e0b68237a48e51cf49882087757bb59916248
Expand Down Expand Up @@ -241,6 +245,7 @@ markupsafe==2.1.1 \
# via
# -r requirements/dev-sdw-requirements.in
# -r requirements/requirements.in
# jinja2
# mako
mccabe==0.7.0 \
--hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
Expand Down
5 changes: 5 additions & 0 deletions requirements/dev-bullseye-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ isort==5.10.1 \
--hash=sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7 \
--hash=sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951
# via -r requirements/dev-sdw-requirements.in
jinja2==3.0.2 \
--hash=sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45 \
--hash=sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c
# via -r requirements/requirements.in
jsonschema==4.17.0 \
--hash=sha256:5bfcf2bca16a087ade17e02b282d34af7ccd749ef76241e7f9bd7c0cb8a9424d \
--hash=sha256:f660066c3966db7d6daeaea8a75e0b68237a48e51cf49882087757bb59916248
Expand Down Expand Up @@ -241,6 +245,7 @@ markupsafe==2.1.1 \
# via
# -r requirements/dev-sdw-requirements.in
# -r requirements/requirements.in
# jinja2
# mako
mccabe==0.7.0 \
--hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
Expand Down
5 changes: 5 additions & 0 deletions requirements/dev-sdw-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ isort==5.10.1 \
--hash=sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7 \
--hash=sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951
# via -r requirements/dev-sdw-requirements.in
jinja2==3.0.2 \
--hash=sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45 \
--hash=sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c
# via -r requirements/requirements.in
jsonschema==4.17.0 \
--hash=sha256:5bfcf2bca16a087ade17e02b282d34af7ccd749ef76241e7f9bd7c0cb8a9424d \
--hash=sha256:f660066c3966db7d6daeaea8a75e0b68237a48e51cf49882087757bb59916248
Expand Down Expand Up @@ -241,6 +245,7 @@ markupsafe==2.1.1 \
# via
# -r requirements/dev-sdw-requirements.in
# -r requirements/requirements.in
# jinja2
# mako
mccabe==0.7.0 \
--hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
Expand Down
1 change: 1 addition & 0 deletions requirements/requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ securedrop-sdk==0.4.0
six==1.11.0
sqlalchemy==1.3.3
urllib3>=1.26.5
jinja2==3.0.2 # per freedomofpress/securedrop#4829953
5 changes: 5 additions & 0 deletions requirements/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ idna==3.2 \
# via
# -r requirements/requirements.in
# requests
jinja2==3.0.2 \
--hash=sha256:827a0e32839ab1600d4eb1c4c33ec5a8edfbc5cb42dafa13b81f182f97784b45 \
--hash=sha256:8569982d3f0889eed11dd620c706d39b60c36d6d25843961f33f77fb6bc6b20c
# via -r requirements/requirements.in
mako==1.2.2 \
--hash=sha256:3724869b363ba630a272a5f89f68c070352137b8fd1757650017b7e06fda163f \
--hash=sha256:8efcb8004681b5f71d09c983ad5a9e6f5c40601a6ec469148753292abc0da534
Expand Down Expand Up @@ -91,6 +95,7 @@ markupsafe==2.0.1 \
--hash=sha256:fa130dd50c57d53368c9d59395cb5526eda596d3ffe36666cd81a44d56e48872
# via
# -r requirements/requirements.in
# jinja2
# mako
pathlib2==2.3.2 \
--hash=sha256:8eb170f8d0d61825e09a95b38be068299ddeda82f35e96c3301a8a5e7604cb83 \
Expand Down
1 change: 1 addition & 0 deletions securedrop_client/conversation/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .transcript import Transcript # noqa: F401
1 change: 1 addition & 0 deletions securedrop_client/conversation/transcript/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
from .transcript import Transcript # noqa: F401
2 changes: 2 additions & 0 deletions securedrop_client/conversation/transcript/items/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from .factory import transcribe # noqa: F401
from .item import Item # noqa: F401
16 changes: 16 additions & 0 deletions securedrop_client/conversation/transcript/items/factory.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import Optional

from securedrop_client import db as database

from .file import File
from .item import Item
from .message import Message


def transcribe(record: database.Base) -> Optional[Item]:
if isinstance(record, database.Message) or isinstance(record, database.Reply):
return Message(record)
if isinstance(record, database.File):
return File(record)
else:
return None
13 changes: 13 additions & 0 deletions securedrop_client/conversation/transcript/items/file.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from securedrop_client import db as database

from .item import Item


class File(Item):
type = "file"

def __init__(self, record: database.File):
super().__init__()

self.filename = record.filename
self.sender = record.source.journalist_designation
11 changes: 11 additions & 0 deletions securedrop_client/conversation/transcript/items/item.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from typing import Optional


class Item:
"""
A transcript item.
Transcript items must define their `type` to be rendered by the transcript template.
"""

type: Optional[str] = None
19 changes: 19 additions & 0 deletions securedrop_client/conversation/transcript/items/message.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from typing import Union

from securedrop_client import db as database

from .item import Item


class Message(Item):
type = "message"

def __init__(self, record: Union[database.Message, database.Reply]):
super().__init__()

self.content = record.content

if isinstance(record, database.Message):
self.sender = record.source.journalist_designation
else:
self.sender = record.journalist.username
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{% if items|length <= 0 %}{% trans %}No messages.{% endtrans %}{% else %}
{% for item in items %}
{% if item.type == "message" %}
{% if loop.changed(item.sender) %}
{% trans sender=item.sender %}{{ sender }} wrote:{% endtrans +%}
{% endif %}
{{ item.content }}
{% elif item.type == "file" %}
{% trans sender=item.sender %}{{ sender }} sent:{% endtrans +%}
{% trans filename=item.filename %}File: {{ filename }}{% endtrans +%}
{% endif %}
{% if not loop.last %}
------
{% endif %}
{% endfor %}
{% endif %}
37 changes: 37 additions & 0 deletions securedrop_client/conversation/transcript/transcript.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import gettext
from typing import Optional

from jinja2 import Environment, PackageLoader, select_autoescape

from securedrop_client import db as database

from .items import Item
from .items import transcribe as transcribe_item

env = Environment(
loader=PackageLoader("securedrop_client.conversation.transcript"),
autoescape=select_autoescape(),
extensions=["jinja2.ext.i18n"],
# Since our plain-text templates have literal whitespace:
lstrip_blocks=True,
trim_blocks=True,
)
env.install_gettext_translations(gettext) # type: ignore [attr-defined]


def transcribe(record: database.Base) -> Optional[Item]:
return transcribe_item(record)


class Transcript:
def __init__(self, conversation: database.Source) -> None:
self._items = list(
filter(
lambda record: record is not None and record.type is not None,
[transcribe(record) for record in conversation.collection],
)
)
self._template = env.get_template("transcript.txt.jinja")

def __str__(self) -> str:
return self._template.render(items=self._items)
15 changes: 15 additions & 0 deletions securedrop_client/locale/messages.pot
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,21 @@ msgstr ""
msgid "Failed to delete source at server"
msgstr ""

msgid "No messages."
msgstr ""

#, python-format
msgid "%(sender)s wrote:"
msgstr ""

#, python-format
msgid "%(sender)s sent:"
msgstr ""

#, python-format
msgid "File: %(filename)s"
msgstr ""

msgid "Download All Files"
msgstr ""

Expand Down
127 changes: 127 additions & 0 deletions tests/test_conversation.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import unittest
from datetime import datetime
from textwrap import dedent

from securedrop_client import conversation
from securedrop_client import db as database


class TestConversationTranscript(unittest.TestCase):
def setUp(self):

source = database.Source(
journalist_designation="happy-bird",
)
files = [
database.File(
filename="4-memo.pdf.gpg",
is_downloaded=True,
),
database.File(
filename="9-memo.zip.gpg",
is_downloaded=True,
),
]
messages = [
database.Message(
filename="1-message.gpg",
is_downloaded=True,
content="Hello! I think this is newsworthy: ...",
),
database.Message(
filename="6-message.gpg",
is_downloaded=True,
content="I can send you more if you're interested.",
),
database.Message(
filename="5-message.gpg",
is_downloaded=True,
content="Here is a document with details!",
),
database.Message(
filename="8-message.gpg",
is_downloaded=True,
content="Sure.",
),
]
interested_journalist = database.User(username="interested-journalist")
other_journalist = database.User(username="other-journalist")
replies = [
database.Reply(
journalist=interested_journalist,
filename="2-reply.gpg",
is_downloaded=True,
content=dedent(
"""\
Thank you for the tip!
Can you tell me more about... ?
"""
),
),
database.Reply(
journalist=interested_journalist,
filename="3-reply.gpg",
is_downloaded=True,
content=dedent(
"""\
Do you have proof of...?
"""
),
),
database.Reply(
journalist=other_journalist,
filename="7-reply.gpg",
is_downloaded=True,
content=dedent("Yes, the document you sent was useful, I'd love to see more."),
),
]
draft_reply = database.DraftReply(
content="Let me think...",
file_counter=2,
timestamp=datetime.now(),
)
source.files = files
source.messages = messages
source.replies = replies
source.draftreplies = [draft_reply]

self._source = source

def test_indicates_explicitly_absence_of_messages(self):
source = database.Source()
assert str(conversation.Transcript(source)) == "No messages."

def test_renders_all_messages(self):
assert str(conversation.Transcript(self._source)) == dedent(
"""\
happy-bird wrote:
Hello! I think this is newsworthy: ...
------
interested-journalist wrote:
Thank you for the tip!
Can you tell me more about... ?
------
Do you have proof of...?
------
happy-bird sent:
File: 4-memo.pdf.gpg
------
happy-bird wrote:
Here is a document with details!
------
I can send you more if you're interested.
------
other-journalist wrote:
Yes, the document you sent was useful, I'd love to see more.
------
happy-bird wrote:
Sure.
------
happy-bird sent:
File: 9-memo.zip.gpg
"""
)

0 comments on commit 95ca10a

Please sign in to comment.