view bitten/notify.py @ 531:5b4a1f1872d3

Import of bitten notify from Ole Trenner. Imported from http://trac.3dbits.de/bittennotify, revision [31]. Source code license change to bsd verified by Ole. Everything looks great. Thanks for the patch.
author wbell
date Sat, 21 Mar 2009 19:05:41 +0000
parents
children 7c1919719538
line wrap: on
line source
#-*- coding: utf-8 -*-
#
# Copyright (C) 2007 Ole Trenner, <ole@jayotee.de>
# All rights reserved.
#
# This software is licensed as described in the file COPYING, which
# you should have received as part of this distribution. 

from trac.core import *
from trac.web.chrome import ITemplateProvider
from trac.config import BoolOption
from trac.notification import NotifyEmail
from bitten.api import IBuildListener
from bitten.model import Build, BuildStep, BuildLog


CONFIG_SECTION = 'notification'
NOTIFY_ON_FAILURE = 'notify_on_failed_build'
NOTIFY_ON_SUCCESS = 'notify_on_successful_build'


class BittenNotify(Component):
    notify_on_failure = BoolOption(CONFIG_SECTION, NOTIFY_ON_FAILURE, 'true',
            """Notify if bitten build fails.""")

    notify_on_success = BoolOption(CONFIG_SECTION, NOTIFY_ON_SUCCESS, 'false',
            """Notify if bitten build succeeds.""")

    def __init__(self):
        self.log.debug('Initializing BittenNotify plugin')


class BittenNotifyDispatcher(Component):
    """Sends notifications on build status by mail."""
    
    implements(IBuildListener, ITemplateProvider)
    
    def __init__(self):
        self.log.debug('Initializing BittenNotify Dispatcher')
        self.email = BittenNotifyEmail(self.env)
        
    def notify(self, build = None):
        self.log.info('BittenNotify invoked for build %r' % build)
        self.log.debug('build status: %s' % build.status)
        if self._should_notify(build):
            self.log.info('Sending notification for build %r' % build)
            build_info = BuildInfo(self.env, build)
            self.email.notify(build_info)
        
    def _should_notify(self, build):
        notify_on_failure = self.config.getbool(CONFIG_SECTION, 
                NOTIFY_ON_FAILURE)
        notify_on_success = self.config.getbool(CONFIG_SECTION, 
                NOTIFY_ON_SUCCESS)
        build_is_failure = (build.status == Build.FAILURE)
        build_is_success = (build.status == Build.SUCCESS)
        return (build_is_failure and notify_on_failure) or \
                (build_is_success and notify_on_success)
    
    # IBuildListener methods
    
    def build_started(self, build):
        """build started"""
        self.notify(build)

    def build_aborted(self, build):
        """build aborted"""
        self.notify(build)

    def build_completed(self, build):
        """build completed"""
        self.notify(build)
        
    # ITemplateProvider methods
    
    def get_templates_dirs(self):
        """Return a list of directories containing the provided template
        files."""
        from pkg_resources import resource_filename
        return [resource_filename(__name__, 'templates')]
        
    def get_htdocs_dirs(self):
        """Return the absolute path of a directory containing additional
        static resources (such as images, style sheets, etc)."""
        return []


class BuildInfo(dict):
    """Wraps a Build instance and exposes properties conveniently"""
    
    readable_states = {Build.SUCCESS:'Successful', Build.FAILURE:'Failed'}
    
    def __init__(self, env, build):
        dict.__init__(self)
        self.build = build
        self.env = env
        self['project_name'] = self.env.project_name 
        self['id'] = self.build.id
        self['status'] = self.readable_states[self.build.status]
        self['link'] = self.env.abs_href.build(self.build.config, 
                self.build.id)
        self['config'] = self.build.config
        self['slave'] = self.build.slave
        self['changeset'] = self.build.rev
        self['changesetlink'] = self.env.abs_href.changeset(self.build.rev)
        self['author'] = self.get_author(build)
        self['errors'] = self.get_errors(build)
        self['faillog'] = self.get_faillog(build)
        
    def get_author(self, build):
        if build and build.rev:
            changeset = self.env.get_repository().get_changeset(build.rev)
            return changeset.author
            
    def get_failed_steps(self, build):
        build_steps = BuildStep.select(self.env, 
                build=build.id, 
                status=BuildStep.FAILURE)
        return build_steps
        
    def get_errors(self, build):
        errors = ''
        for step in self.get_failed_steps(build):
            errors += ', '.join(['%s: %s' % (step.name, error) \
                    for error in step.errors])
        return errors
                               
    def get_faillog(self, build):
        faillog = ''
        for step in self.get_failed_steps(build):
            build_logs = BuildLog.select(self.env, 
                    build=build.id, 
                    step=step.name)
            for log in build_logs:
                faillog += '\n'.join(['%5s: %s' % (level, msg) \
                        for level, msg in log.messages])
        return faillog
                               
    def __getattr__(self, attr):
        return dict.__getitem__(self,attr)
    
    def __repr__(self):
        repr = ''
        for k, v in self.items():
            repr += '%s: %s\n' % (k, v)
        return repr
    
    def __str__(self):
        return self.repr()


class BittenNotifyEmail(NotifyEmail):
    """Notification of failed builds."""

    template_name = 'bitten_notify_email.txt'
    from_email = 'bitten@localhost'
    
    def __init__(self, env):
        NotifyEmail.__init__(self, env)

    def notify(self, build_info):
        self.build_info = build_info
        self.data = self.build_info
        subject = '[%s Build] %s [%s] %s' % (self.build_info.status,
                self.env.project_name, 
                self.build_info.changeset, 
                self.build_info.config)
        stream = self.template.generate(**self.data) 
        body = stream.render('text') 
        self.env.log.debug('notification: %s' % body )         
        NotifyEmail.notify(self, self.build_info.id, subject)

    def get_recipients(self, resid):
        author = self.build_info.author
        users = {}
        [users.__setitem__(username, email) for username, name, email in self.env.get_known_users(None)]
        if (author in users.keys() and users[author]):
            author = users[author]
        torecipients = [author]
        ccrecipients = []
        return (torecipients, ccrecipients)

    def send(self, torcpts, ccrcpts, mime_headers={}):
        mime_headers = {}
        mime_headers['X-Trac-Build-ID'] = str(self.build_info.id)
        mime_headers['X-Trac-Build-URL'] = self.build_info.link
        NotifyEmail.send(self, torcpts, ccrcpts, mime_headers)
Copyright (C) 2012-2017 Edgewall Software