Changeset - 3a221939a19f
[Not reviewed]
default
0 5 0
Mads Kiilerich - 9 years ago 2016-07-28 16:30:28
madski@unity3d.com
notifications: make more template strings available for mails

To be used soon ...
5 files changed with 74 insertions and 12 deletions:
0 comments (0 inline, 0 general)
kallithea/lib/helpers.py
Show inline comments
 
@@ -80,50 +80,54 @@ def canonical_url(*args, **kargs):
 
    return url(*args, **kargs)
 

	
 
def canonical_hostname():
 
    '''Return canonical hostname of system'''
 
    from kallithea import CONFIG
 
    try:
 
        parts = CONFIG.get('canonical_url', '').split('://', 1)
 
        return parts[1].split('/', 1)[0]
 
    except IndexError:
 
        parts = url('home', qualified=True).split('://', 1)
 
        return parts[1].split('/', 1)[0]
 

	
 
def html_escape(s):
 
    """Return string with all html escaped.
 
    This is also safe for javascript in html but not necessarily correct.
 
    """
 
    return (s
 
        .replace('&', '&')
 
        .replace(">", ">")
 
        .replace("<", "&lt;")
 
        .replace('"', "&quot;")
 
        .replace("'", "&apos;")
 
        )
 

	
 
def shorter(s, size=20):
 
    postfix = '...'
 
def shorter(s, size=20, firstline=False, postfix='...'):
 
    """Truncate s to size, including the postfix string if truncating.
 
    If firstline, truncate at newline.
 
    """
 
    if firstline:
 
        s = s.split('\n', 1)[0].rstrip()
 
    if len(s) > size:
 
        return s[:size - len(postfix)] + postfix
 
    return s
 

	
 

	
 
def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
 
    """
 
    Reset button
 
    """
 
    _set_input_attrs(attrs, type, name, value)
 
    _set_id_attr(attrs, id, name)
 
    convert_boolean_attrs(attrs, ["disabled"])
 
    return HTML.input(**attrs)
 

	
 
reset = _reset
 
safeid = _make_safe_id_component
 

	
 

	
 
def FID(raw_id, path):
 
    """
 
    Creates a unique ID for filenode based on it's hash of path and revision
 
    it's safe to use in urls
 

	
 
    :param raw_id:
kallithea/model/comment.py
Show inline comments
 
@@ -67,105 +67,112 @@ class ChangesetCommentsModel(BaseModel):
 
        if revision:
 
            notification_type = Notification.TYPE_CHANGESET_COMMENT
 
            cs = repo.scm_instance.get_changeset(revision)
 
            desc = cs.short_id
 

	
 
            threading = ['%s-rev-%s@%s' % (repo.repo_name, revision, h.canonical_hostname())]
 
            if line_no: # TODO: url to file _and_ line number
 
                threading.append('%s-rev-%s-line-%s@%s' % (repo.repo_name, revision, line_no,
 
                                                           h.canonical_hostname()))
 
            comment_url = h.canonical_url('changeset_home',
 
                repo_name=repo.repo_name,
 
                revision=revision,
 
                anchor='comment-%s' % comment.comment_id)
 
            subj = safe_unicode(
 
                h.link_to('Re changeset: %(desc)s %(line)s' % \
 
                          {'desc': desc, 'line': line},
 
                          comment_url)
 
            )
 
            # get the current participants of this changeset
 
            recipients = ChangesetComment.get_users(revision=revision)
 
            # add changeset author if it's known locally
 
            cs_author = User.get_from_cs_author(cs.author)
 
            if not cs_author:
 
                #use repo owner if we cannot extract the author correctly
 
                # FIXME: just use committer name even if not a user
 
                cs_author = repo.user
 
            recipients += [cs_author]
 
            email_kwargs = {
 
                'status_change': status_change,
 
                'cs_comment_user': user.full_name_and_username,
 
                'cs_target_repo': h.canonical_url('summary_home', repo_name=repo.repo_name),
 
                'cs_comment_url': comment_url,
 
                'raw_id': revision,
 
                'message': cs.message,
 
                'cs_author': cs_author,
 
                'repo_name': repo.repo_name,
 
                'short_id': h.short_id(revision),
 
                'branch': cs.branch,
 
                'comment_username': user.username,
 
                'threading': threading,
 
            }
 
        #pull request
 
        elif pull_request:
 
            notification_type = Notification.TYPE_PULL_REQUEST_COMMENT
 
            desc = comment.pull_request.title
 
            _org_ref_type, org_ref_name, _org_rev = comment.pull_request.org_ref.split(':')
 
            _other_ref_type, other_ref_name, _other_rev = comment.pull_request.other_ref.split(':')
 
            threading = ['%s-pr-%s@%s' % (pull_request.other_repo.repo_name,
 
                                          pull_request.pull_request_id,
 
                                          h.canonical_hostname())]
 
            if line_no: # TODO: url to file _and_ line number
 
                threading.append('%s-pr-%s-line-%s@%s' % (pull_request.other_repo.repo_name,
 
                                                          pull_request.pull_request_id, line_no,
 
                                                          h.canonical_hostname()))
 
            comment_url = pull_request.url(canonical=True,
 
                anchor='comment-%s' % comment.comment_id)
 
            subj = safe_unicode(
 
                h.link_to('Re pull request %(pr_nice_id)s: %(desc)s %(line)s' % \
 
                          {'desc': desc,
 
                           'pr_nice_id': comment.pull_request.nice_id(),
 
                           'line': line},
 
                          comment_url)
 
            )
 
            # get the current participants of this pull request
 
            recipients = ChangesetComment.get_users(pull_request_id=
 
                                                pull_request.pull_request_id)
 
            # add pull request author
 
            recipients += [pull_request.owner]
 

	
 
            # add the reviewers to notification
 
            recipients += pull_request.get_reviewer_users()
 

	
 
            #set some variables for email notification
 
            email_kwargs = {
 
                'pr_title': pull_request.title,
 
                'pr_nice_id': pull_request.nice_id(),
 
                'status_change': status_change,
 
                'closing_pr': closing_pr,
 
                'pr_comment_url': comment_url,
 
                'pr_comment_user': user.full_name_and_username,
 
                'pr_target_repo': h.canonical_url('summary_home',
 
                                   repo_name=pull_request.other_repo.repo_name),
 
                'pr_target_branch': other_ref_name,
 
                'pr_source_repo': h.canonical_url('summary_home',
 
                                   repo_name=pull_request.org_repo.repo_name),
 
                'pr_source_branch': org_ref_name,
 
                'pr_owner': pull_request.owner,
 
                'repo_name': pull_request.other_repo.repo_name,
 
                'ref': org_ref_name,
 
                'comment_username': user.username,
 
                'threading': threading,
 
            }
 

	
 
        return subj, body, recipients, notification_type, email_kwargs
 

	
 
    def create(self, text, repo, user, revision=None, pull_request=None,
 
               f_path=None, line_no=None, status_change=None, closing_pr=False,
 
               send_email=True):
 
        """
 
        Creates a new comment for either a changeset or a pull request.
 
        status_change and closing_pr is only for the optional email.
 

	
 
        Returns the created comment.
 
        """
 
        if not status_change and not text:
 
            log.warning('Missing text for comment, skipping...')
 
            return None
 

	
 
        repo = self._get_repo(repo)
 
        user = self._get_user(user)
 
        comment = ChangesetComment()
 
        comment.repo = repo
 
        comment.author = user
kallithea/model/notification.py
Show inline comments
 
@@ -283,50 +283,50 @@ class EmailNotificationModel(BaseModel):
 
    TYPE_PASSWORD_RESET = 'password_link'
 
    TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
 
    TYPE_PULL_REQUEST = Notification.TYPE_PULL_REQUEST
 
    TYPE_PULL_REQUEST_COMMENT = Notification.TYPE_PULL_REQUEST_COMMENT
 
    TYPE_DEFAULT = 'default'
 

	
 
    def __init__(self):
 
        super(EmailNotificationModel, self).__init__()
 
        self._template_root = kallithea.CONFIG['pylons.paths']['templates'][0]
 
        self._tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
 
        self.email_types = {
 
            self.TYPE_CHANGESET_COMMENT: 'changeset_comment',
 
            self.TYPE_PASSWORD_RESET: 'password_reset',
 
            self.TYPE_REGISTRATION: 'registration',
 
            self.TYPE_DEFAULT: 'default',
 
            self.TYPE_PULL_REQUEST: 'pull_request',
 
            self.TYPE_PULL_REQUEST_COMMENT: 'pull_request_comment',
 
        }
 
        self._subj_map = {
 
            self.TYPE_CHANGESET_COMMENT: _('[Comment] %(repo_name)s changeset %(short_id)s on %(branch)s'),
 
            self.TYPE_MESSAGE: 'Test Message',
 
            # self.TYPE_PASSWORD_RESET
 
            self.TYPE_REGISTRATION: _('New user %(new_username)s registered'),
 
            # self.TYPE_DEFAULT
 
            self.TYPE_PULL_REQUEST: _('[Added] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s'),
 
            self.TYPE_PULL_REQUEST_COMMENT: _('[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(ref)s'),
 
            self.TYPE_PULL_REQUEST: _('[Added] %(repo_name)s pull request %(pr_nice_id)s from %(pr_source_branch)s'),
 
            self.TYPE_PULL_REQUEST_COMMENT: _('[Comment] %(repo_name)s pull request %(pr_nice_id)s from %(pr_source_branch)s'),
 
        }
 

	
 
    def get_email_description(self, type_, **kwargs):
 
        """
 
        return subject for email based on given type
 
        """
 
        tmpl = self._subj_map[type_]
 
        try:
 
            subj = tmpl % kwargs
 
        except KeyError as e:
 
            log.error('error generating email subject for %r from %s: %s', type_, ','.join(self._subj_map.keys()), e)
 
            raise
 
        l = [safe_unicode(x) for x in [kwargs.get('status_change'), kwargs.get('closing_pr') and _('Closing')] if x]
 
        if l:
 
            if subj.startswith('['):
 
                subj = '[' + ', '.join(l) + ': ' + subj[1:]
 
            else:
 
                subj = '[' + ', '.join(l) + '] ' + subj
 
        return subj
 

	
 
    def get_email_tmpl(self, type_, content_type, **kwargs):
 
        """
 
        return generated template for email based on given type
 
        """
kallithea/model/pull_request.py
Show inline comments
 
@@ -132,58 +132,65 @@ class PullRequestModel(BaseModel):
 
        # reviewers and mention_recipients should be sets of User objects.
 
        #members
 
        for reviewer in reviewers:
 
            reviewer = PullRequestReviewers(reviewer, pr)
 
            Session().add(reviewer)
 

	
 
        revision_data = [(x.raw_id, x.message)
 
                         for x in map(pr.org_repo.get_changeset, pr.revisions)]
 

	
 
        #notification to reviewers
 
        pr_url = pr.url(canonical=True)
 
        threading = ['%s-pr-%s@%s' % (pr.other_repo.repo_name,
 
                                      pr.pull_request_id,
 
                                      h.canonical_hostname())]
 
        subject = safe_unicode(
 
            h.link_to(
 
              _('%(user)s wants you to review pull request %(pr_nice_id)s: %(pr_title)s') % \
 
                {'user': user.username,
 
                 'pr_title': pr.title,
 
                 'pr_nice_id': pr.nice_id()},
 
                pr_url)
 
            )
 
        body = pr.description
 
        _org_ref_type, org_ref_name, _org_rev = pr.org_ref.split(':')
 
        _other_ref_type, other_ref_name, _other_rev = pr.other_ref.split(':')
 
        email_kwargs = {
 
            'pr_title': pr.title,
 
            'pr_user_created': user.full_name_and_username,
 
            'pr_repo_url': h.canonical_url('summary_home', repo_name=pr.other_repo.repo_name),
 
            'pr_url': pr_url,
 
            'pr_revisions': revision_data,
 
            'repo_name': pr.other_repo.repo_name,
 
            'org_repo_name': pr.org_repo.repo_name,
 
            'pr_nice_id': pr.nice_id(),
 
            'ref': org_ref_name,
 
            'pr_target_repo': h.canonical_url('summary_home',
 
                               repo_name=pr.other_repo.repo_name),
 
            'pr_target_branch': other_ref_name,
 
            'pr_source_repo': h.canonical_url('summary_home',
 
                               repo_name=pr.org_repo.repo_name),
 
            'pr_source_branch': org_ref_name,
 
            'pr_owner': pr.owner,
 
            'pr_username': user.username,
 
            'threading': threading,
 
            'is_mention': False,
 
            }
 
        if reviewers:
 
            NotificationModel().create(created_by=user, subject=subject, body=body,
 
                                       recipients=reviewers,
 
                                       type_=Notification.TYPE_PULL_REQUEST,
 
                                       email_kwargs=email_kwargs)
 

	
 
        if mention_recipients:
 
            mention_recipients.difference_update(reviewers)
 
        if mention_recipients:
 
            email_kwargs['is_mention'] = True
 
            subject = _('[Mention]') + ' ' + subject
 
            NotificationModel().create(created_by=user, subject=subject, body=body,
 
                                       recipients=mention_recipients,
 
                                       type_=Notification.TYPE_PULL_REQUEST,
 
                                       email_kwargs=email_kwargs)
 

	
 
    def mention_from_description(self, user, pr, old_description=''):
 
        mention_recipients = (extract_mentioned_users(pr.description) -
 
                              extract_mentioned_users(old_description))
 

	
kallithea/tests/models/test_notifications.py
Show inline comments
 
@@ -161,57 +161,101 @@ class TestNotifications(TestController):
 
        Session().commit()
 

	
 
        assert NotificationModel().get_unread_cnt_for_user(self.u1) == 0
 
        assert NotificationModel().get_unread_cnt_for_user(self.u2) == 1
 
        assert NotificationModel().get_unread_cnt_for_user(self.u3) == 2
 

	
 
    @mock.patch.object(h, 'canonical_url', (lambda arg, **kwargs: 'http://%s/?%s' % (arg, '&'.join('%s=%s' % (k, v) for (k, v) in sorted(kwargs.items())))))
 
    def test_dump_html_mails(self):
 
        # Exercise all notification types and dump them to one big html file
 
        l = []
 

	
 
        def send_email(recipients, subject, body='', html_body='', headers=None, author=None):
 
            l.append('\n\n<h1>%s</h1>\n' % desc) # desc is from outer scope
 
            l.append('<pre>\n\n')
 
            l.append('From: %s\n' % author.username)
 
            l.append('To: %s\n' % ' '.join(recipients))
 
            l.append('Subject: %s\n' % subject)
 
            l.append('\n--------------------\n%s\n--------------------' % body)
 
            l.append('</pre>\n')
 
            l.append('\n%s\n' % html_body)
 
            l.append('<pre>--------------------</pre>\n')
 

	
 
        l.append('<html><body>\n')
 
        with mock.patch.object(kallithea.lib.celerylib.tasks, 'send_email', send_email):
 
            pr_kwargs = dict(pr_nice_id='#7', ref='devbranch', org_repo_name='repo_org', pr_title='The Title', pr_url='http://pr.org/7')
 
            pr_kwargs = dict(
 
                pr_nice_id='#7',
 
                pr_title='The Title',
 
                pr_url='http://pr.org/7',
 
                pr_target_repo='http://mainline.com/repo',
 
                pr_target_branch='trunk',
 
                pr_source_repo='https://dev.org/repo',
 
                pr_source_branch='devbranch',
 
                pr_owner=User.get(self.u2),
 
                )
 

	
 
            for type_, body, kwargs in [
 
                (Notification.TYPE_CHANGESET_COMMENT, u'This is the new comment.\n\n - and here it ends indented.', dict(short_id='cafe1234', raw_id='cafe1234c0ffeecafe', branch='brunch', cs_comment_user='Opinionated User (jsmith)', cs_comment_url='http://comment.org', is_mention=[False, True], message='This changeset did something clever which is hard to explain', status_change=[None, 'Approved'], cs_target_repo='repo_target', cs_url='http://changeset.com')),
 
                (Notification.TYPE_MESSAGE, u'This is the body of the test message\n - nothing interesting here except indentation.', dict()),
 
                (Notification.TYPE_CHANGESET_COMMENT,
 
                 u'This is the new comment.\n\n - and here it ends indented.',
 
                 dict(
 
                    short_id='cafe1234',
 
                    raw_id='cafe1234c0ffeecafe',
 
                    branch='brunch',
 
                    cs_comment_user='Opinionated User (jsmith)',
 
                    cs_comment_url='http://comment.org',
 
                    is_mention=[False, True],
 
                    message='This changeset did something clever which is hard to explain',
 
                    status_change=[None, 'Approved'],
 
                    cs_target_repo='repo_target',
 
                    cs_url='http://changeset.com',
 
                    cs_author=User.get(self.u2))),
 
                (Notification.TYPE_MESSAGE,
 
                 u'This is the body of the test message\n - nothing interesting here except indentation.',
 
                 dict()),
 
                #(Notification.TYPE_MENTION, '$body', None), # not used
 
                (Notification.TYPE_REGISTRATION, u'Registration body', dict(new_username='newbie', registered_user_url='http://newbie.org', new_email='new@email.com', new_full_name='New Full Name')),
 
                (Notification.TYPE_PULL_REQUEST, u'This PR is awesome because it does stuff\n - please approve indented!', dict(pr_user_created='Requesting User (root)', is_mention=[False, True], pr_revisions=[('123abc'*7, "Introduce one and two\n\nand that's it"), ('567fed'*7, 'Make one plus two equal tree')], **pr_kwargs)),
 
                (Notification.TYPE_PULL_REQUEST_COMMENT, u'Me too!\n\n - and indented on second line', dict(closing_pr=[False, True], pr_comment_user='Opinionated User (jsmith)', pr_comment_url='http://pr.org/comment', status_change=[None, 'Under Review'], pr_target_repo='http://target.com/repo', **pr_kwargs)),
 
                (Notification.TYPE_REGISTRATION,
 
                 u'Registration body',
 
                 dict(
 
                    new_username='newbie',
 
                    registered_user_url='http://newbie.org',
 
                    new_email='new@email.com',
 
                    new_full_name='New Full Name')),
 
                (Notification.TYPE_PULL_REQUEST,
 
                 u'This PR is awesome because it does stuff\n - please approve indented!',
 
                 dict(
 
                    pr_user_created='Requesting User (root)', # pr_owner should perhaps be used for @mention in description ...
 
                    is_mention=[False, True],
 
                    pr_revisions=[('123abc'*7, "Introduce one and two\n\nand that's it"), ('567fed'*7, 'Make one plus two equal tree')],
 
                    org_repo_name='repo_org',
 
                    **pr_kwargs)),
 
                (Notification.TYPE_PULL_REQUEST_COMMENT,
 
                 u'Me too!\n\n - and indented on second line',
 
                 dict(
 
                    closing_pr=[False, True],
 
                    pr_comment_user='Opinionated User (jsmith)',
 
                    pr_comment_url='http://pr.org/comment',
 
                    status_change=[None, 'Under Review'],
 
                    **pr_kwargs)),
 
                ]:
 
                kwargs['repo_name'] = u'repo/name'
 
                params = [(type_, type_, body, kwargs)]
 
                for param_name in ['is_mention', 'status_change', 'closing_pr']: # TODO: inline/general
 
                    if not isinstance(kwargs.get(param_name), list):
 
                        continue
 
                    new_params = []
 
                    for v in kwargs[param_name]:
 
                        for desc, type_, body, kwargs in params:
 
                            kwargs = dict(kwargs)
 
                            kwargs[param_name] = v
 
                            new_params.append(('%s, %s=%r' % (desc, param_name, v), type_, body, kwargs))
 
                    params = new_params
 

	
 
                for desc, type_, body, kwargs in params:
 
                    # desc is used as "global" variable
 
                    notification = NotificationModel().create(created_by=self.u1,
 
                                                       subject=u'unused', body=body, email_kwargs=kwargs,
 
                                                       recipients=[self.u2], type_=type_)
 

	
 
            # Email type TYPE_PASSWORD_RESET has no corresponding notification type - test it directly:
 
            desc = 'TYPE_PASSWORD_RESET'
 
            kwargs = dict(user='John Doe', reset_token='decbf64715098db5b0bd23eab44bd792670ab746', reset_url='http://reset.com/decbf64715098db5b0bd23eab44bd792670ab746')
 
            kallithea.lib.celerylib.run_task(kallithea.lib.celerylib.tasks.send_email, ['john@doe.com'],
0 comments (0 inline, 0 general)