Skip to content

Commit e962ba0

Browse files
committed
Refactored CodeCommentNotifyEmail to use subscriptions
Added a `for_comment` query helper. Changed the notification scheme so that resource authors **always** receive a "to" notification. Changed the `notify` column to be a boolean. Changed the `for_*` signatures to accept an optional `notify` flag. Changed the `select` signature to accept an optional `notify` flag, and the method to include the flag in the query. Changed the `select` method to handle `dict`, `list, and `bool` query arguments.
1 parent 9408d5c commit e962ba0

File tree

4 files changed

+52
-114
lines changed

4 files changed

+52
-114
lines changed

code_comments/db.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@
3131
Column('path'),
3232
Column('repos'),
3333
Column('rev'),
34-
Column('notify'),
34+
Column('notify', type='bool'),
3535
Index(['user']),
3636
Index(['path']),
3737
],

code_comments/notification.py

Lines changed: 12 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
from trac.attachment import Attachment
21
from trac.config import BoolOption
32
from trac.core import Component, implements
43
from trac.notification import NotifyEmail
5-
from trac.resource import ResourceNotFound
6-
from trac.versioncontrol import RepositoryManager, NoSuchChangeset
4+
75
from code_comments.api import ICodeCommentChangeListener
86
from code_comments.comments import Comments
7+
from code_comments.subscription import Subscription
98

109

1110
class CodeCommentChangeListener(Component):
@@ -33,41 +32,6 @@ class CodeCommentNotifyEmail(NotifyEmail):
3332
template_name = "code_comment_notify_email.txt"
3433
from_email = "trac+comments@localhost"
3534

36-
def _get_attachment_author(self, parent, parent_id, filename):
37-
"""
38-
Returns the author of a given attachment.
39-
"""
40-
try:
41-
attachment = Attachment(self.env, parent, parent_id, filename)
42-
return attachment.author
43-
except ResourceNotFound:
44-
self.env.log.debug("Invalid attachment, unable to determine "
45-
"author.")
46-
47-
def _get_changeset_author(self, revision, reponame=None):
48-
"""
49-
Returns the author of a changeset for a given revision.
50-
"""
51-
try:
52-
repos = RepositoryManager(self.env).get_repository(reponame)
53-
changeset = repos.get_changeset(revision)
54-
return changeset.author
55-
except NoSuchChangeset:
56-
self.env.log.debug("Invalid changeset, unable to determine author")
57-
58-
def _get_original_author(self, comment):
59-
"""
60-
Returns the author for the target of a given comment.
61-
"""
62-
if comment.type == 'attachment':
63-
parent, parent_id, filename = comment.path.split("/")[1:]
64-
return self._get_attachment_author(parent, parent_id,
65-
filename)
66-
elif (comment.type == 'changeset' or comment.type == "browser"):
67-
# TODO: When support is added for multiple repositories, this
68-
# will need updated
69-
return self._get_changeset_author(comment.revision)
70-
7135
def _get_comment_thread(self, comment):
7236
"""
7337
Returns all comments in the same location as a given comment, sorted
@@ -80,16 +44,6 @@ def _get_comment_thread(self, comment):
8044
'line': comment.line}
8145
return comments.search(args, order_by='id')
8246

83-
def _get_commenters(self, comment):
84-
"""
85-
Returns a list of all commenters for the same thing.
86-
"""
87-
comments = Comments(None, self.env)
88-
args = {'type': comment.type,
89-
'revision': comment.revision,
90-
'path': comment.path}
91-
return comments.get_all_comment_authors(comments.search(args))
92-
9347
def get_recipients(self, comment):
9448
"""
9549
Determine who should receive the notification.
@@ -99,31 +53,27 @@ def get_recipients(self, comment):
9953
Current scheme is as follows:
10054
10155
* For the first comment in a given location, the notification is sent
102-
'to' the original author of the thing being commented on, and 'copied'
103-
to the authors of any other comments on that thing
56+
'to' the author of the resource being commented on, and 'copied'
57+
to any other subscribers to that resource
10458
* For any further comments in a given location, the notification is
10559
sent 'to' the author of the last comment in that location, and
106-
'copied' to both the original author of the thing and the authors of
107-
any other comments on that thing
60+
the author of the resource and 'copied' to any other subscribers
10861
"""
10962
torcpts = set()
63+
ccrcpts = set()
11064

111-
# Get the original author
112-
original_author = self._get_original_author(comment)
113-
114-
# Get other commenters
115-
ccrcpts = set(self._get_commenters(comment))
65+
for subscription in Subscription.for_comment(self.env, comment,
66+
notify=True):
67+
if subscription.role == 'author':
68+
torcpts.add(subscription.user)
69+
else:
70+
ccrcpts.add(subscription.user)
11671

11772
# Is this a reply, or a new comment?
11873
thread = self._get_comment_thread(comment)
11974
if len(thread) > 1:
12075
# The author of the comment before this one
12176
torcpts.add(thread[-2].author)
122-
# Copy to the original author
123-
ccrcpts.add(original_author)
124-
else:
125-
# This is the first comment in this thread
126-
torcpts.add(original_author)
12777

12878
# Should we notify the comment author?
12979
if not self.notify_self:

code_comments/subscription.py

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,23 +19,30 @@ class Subscription(object):
1919
path = ''
2020
rev = ''
2121
repos = ''
22-
notify = 'always'
22+
notify = True
2323

2424
def __init__(self, env, data=None):
2525
if isinstance(data, dict):
2626
self.__dict__ = data
2727
self.env = env
2828

2929
@classmethod
30-
def select(cls, env, args={}):
30+
def select(cls, env, args={}, notify=None):
3131
select = 'SELECT * FROM code_comments_subscriptions'
32+
if notify:
33+
args['notify'] = bool(notify)
3234
if len(args) > 0:
3335
select += ' WHERE '
3436
criteria = []
3537
for key, value in args.iteritems():
3638
template = '{0}={1}'
3739
if isinstance(value, str):
3840
template = '{0}=\'{1}\''
41+
if (isinstance(value, tuple) or isinstance(value, list)):
42+
template = '{0} IN (\'{1}\')'
43+
value = '\',\''.join(value)
44+
if isinstance(value, bool):
45+
value = int(value)
3946
criteria.append(template.format(key, value))
4047
select += ' AND '.join(criteria)
4148
cursor = env.get_read_db().cursor()
@@ -112,7 +119,7 @@ def _from_row(cls, env, row):
112119
subscription.path = row[4]
113120
subscription.repos = row[5]
114121
subscription.rev = row[6]
115-
subscription.notify = row[7]
122+
subscription.notify = bool(row[7])
116123
return subscription
117124
except IndexError:
118125
# Invalid row
@@ -145,7 +152,7 @@ def _from_dict(cls, env, dict_):
145152

146153
@classmethod
147154
def from_attachment(cls, env, attachment, user=None, role='author',
148-
notify='always'):
155+
notify=True):
149156
"""
150157
Creates a subscription from an Attachment object.
151158
"""
@@ -166,7 +173,7 @@ def from_attachment(cls, env, attachment, user=None, role='author',
166173

167174
@classmethod
168175
def from_changeset(cls, env, changeset, user=None, role='author',
169-
notify='always'):
176+
notify=True):
170177
"""
171178
Creates a subscription from a Changeset object.
172179
"""
@@ -183,13 +190,13 @@ def from_changeset(cls, env, changeset, user=None, role='author',
183190

184191
@classmethod
185192
def from_comment(cls, env, comment, user=None, role='commenter',
186-
notify='always'):
193+
notify=True):
187194
"""
188195
Creates a subscription from a Comment object.
189196
"""
190197
sub = {
191198
'user': user or comment.author,
192-
'role': user,
199+
'role': 'commenter',
193200
'type': comment.type,
194201
'notify': notify,
195202
}
@@ -221,7 +228,7 @@ def from_comment(cls, env, comment, user=None, role='commenter',
221228
return cls._from_dict(env, sub)
222229

223230
@classmethod
224-
def for_attachment(cls, env, attachment, path=None):
231+
def for_attachment(cls, env, attachment, path=None, notify=None):
225232
"""
226233
Returns all subscriptions for an attachment. The path can be
227234
overridden.
@@ -234,10 +241,10 @@ def for_attachment(cls, env, attachment, path=None):
234241
'type': 'attachment',
235242
'path': _path,
236243
}
237-
return cls.select(env, args)
244+
return cls.select(env, args, notify)
238245

239246
@classmethod
240-
def for_changeset(cls, env, changeset):
247+
def for_changeset(cls, env, changeset, notify=None):
241248
"""
242249
Returns all subscriptions for an changeset.
243250
"""
@@ -246,7 +253,28 @@ def for_changeset(cls, env, changeset):
246253
'repos': changeset.repos.reponame,
247254
'rev': changeset.rev,
248255
}
249-
return cls.select(env, args)
256+
return cls.select(env, args, notify)
257+
258+
@classmethod
259+
def for_comment(cls, env, comment, notify=None):
260+
"""
261+
Return all subscriptions for a comment.
262+
"""
263+
args = {}
264+
if comment.type == 'attachment':
265+
args['type'] = comment.type
266+
args['path'] = comment.path.split(':')[1]
267+
268+
if comment.type == 'changeset':
269+
args['type'] = comment.type
270+
args['rev'] = str(comment.revision)
271+
272+
if comment.type == 'browser':
273+
args['type'] = ('browser', 'changeset')
274+
args['path'] = (comment.path, '')
275+
args['rev'] = str(comment.revision)
276+
277+
return cls.select(env, args, notify)
250278

251279

252280
class SubscriptionAdmin(Component):

todo.md

Lines changed: 0 additions & 40 deletions
This file was deleted.

0 commit comments

Comments
 (0)