wiki:POTT/Ablaufende Tickets

Eskalierende/ablaufende Tickets im POTT

In den Tickets #31 und #567 wird ein oft formulierter Wunsch diskutiert, und zwar der danach, dass man Tickets Daten geben kann, wann sie einen erinnern per E-Mail sollen. In einer ersten Fassung wurde das zum 04.04.2013 mal umgesetzt. Das "Trac-Plugin" besteht aus zwei Teilen:

  • Ein weiteres Feld "Erinnerungsdatum", wo man ein Datum eintragen kann
  • Einem Cronjob, der jeden Tag schaut, welche Tickets alle an dem Tag ihr "Erinnerungsdatum" gesetzt haben und dann einfach eine E-Mail an alle Leute auf CC (oder falls nicht vorhanden den Eigentümer/Schreiber) sendet.

So sieht das dann aus (Ausschnitt der Seite »Neues Ticket anlegen«):

Ein Screenshot-Ausschnitt

Sourcecode

Hier eine Version der Datei due_ticket_checker.py (kommt vielleicht in git oder so):

#!/usr/bin/env python
#
# POTT #31: Erinnerungsfunktion fuer Tickets. Taeglich per Cron
# job ausgefuehrt.
# Sven Koeppel, 04.04.2013, GPL

# config:
domain = "elearning.physik.uni-frankfurt.de"
rpc_url = "https://benutzername:passwort@elearning.physik.uni-frankfurt.de/projekt/login/xmlrpc"
template_dir = "/var/www/trac/templates"
template_file = "ticket_due_email.txt"
trac_url = "https://elearning.physik.uni-frankfurt.de/projekt"

# the date to use in this script
from datetime import date
date = date.today().strftime("%d.%m.%Y")

import re
import xmlrpclib # has other name in Python3!

# Use genshi in nice trac way
from genshi.template import TemplateLoader
from genshi.template.text import NewTextTemplate
loader = TemplateLoader([ template_dir ], auto_reload=True, variable_lookup='lenient')
tmpl = loader.load(template_file, cls=NewTextTemplate)


def find_dangling_due_tickets(pott_rpc, up_to_date):
        # looks for any dangling due tickets which are up_to that date (string like 01.01.2013).
        # that is, the up_to_date is included.
        # dangling tickets are tickets where nobody made an edit after the due.

        # find all tickets having a due date: look for "due date contains dot" (german date separator)
        all_due_tickets = pott_rpc.ticket.query("due_date=~.")

        # get all properties from that tickets (That's really slow)
        all_attr = [ get_attributes(pott_rpc, i) for i in all_due_tickets ]

        # take a deep look into last change time and due_date time.
        # Compare and take a shit of that.

def find_due_tickets_with_date(pott_rpc, date):
        ticketlist = pott_rpc.ticket.query("due_date=%s" % str(date))
        return [ get_attributes(pott_rpc, i) for i in ticketlist ]

def get_attributes(pott_rpc, ticket_id):
        # rpc.ticket.get(i): [id, date, date, attributes]
        attr = pott_rpc.ticket.get(ticket_id)[3]
        # inject various data
        attr['id'] = ticket_id
        attr['url'] = "%s/ticket/%s" % (trac_url, ticket_id)
        return attr     

def send_due_mail_for_ticket(ticket_attributes):
        stream = tmpl.generate(ticket=ticket_attributes, trac_url=trac_url)
        ugly_ticket_to_mail(ticket_attributes, stream.render())

def ugly_ticket_to_mail(ticket_attributes, rendered_template):
        a = ticket_attributes # shorthand to named attributes
        to = a['owner'].strip()
        cc = re.compile("\s*,\s*").split( a['cc'] )
        # poor man's "auto append domain" if cc list is like
        # "frank, hans" => ["frank@domain.tld", "hans@domain.tld"]
        append_url = lambda u: "%s <%s@%s>" % (u, u, domain) if not "@" in u else u
        cc = filter(lambda s: s.strip(), cc) # strip white entries ("\s+")
        cc = [ append_url(u) for u in cc ] # append @domain.tld each
        if to: to = append_url(to) # leave empty if unset

        # other mimic Trac internal notification system
        subject = "POTT #%s: %s" % (a['id'], a['summary'])
        sender_mail = "pott@%s" % domain
        sender_nice = "POTT Erinnerung <%s>" % sender_mail

        # check if there are recipients.
        # CC and Owner may be empty, then reporter should get the mail.
        # this is very different to trac notifications.
        if not to and not cc: to = append_url(a['reporter'])

        my_sendmail(to=to, subject=subject, body=rendered_template, sender=sender_nice, cc=cc)

def my_sendmail(to, subject, body, sender, cc):
        # verwendet /bin/sendmail
        # cc is supposed to be a list of strings.
        import smtplib
        from email.mime.text import MIMEText

        msg = MIMEText(body, 'plain', 'utf-8')
        msg['Subject'] = subject
        msg['From'] = sender
        if to: msg['To'] = to
        msg["Reply-To"] = sender
        if cc: msg['Cc'] = ", ".join(cc)
        recvs = [to] + cc if to else cc

        try:
                s = smtplib.SMTP('localhost')
                s.sendmail(msg['From'], recvs, msg.as_string())
                s.quit()
        except Exception:
                print "Failed to send mail"
                print "Wanted to send to %s mail:\n%s" % (recvs, msg.as_string())


# main namespace shit
pott =  xmlrpclib.ServerProxy(rpc_url)

def test_with_ticket(number):
        a = get_attributes(pott, number)
        print a
        send_due_mail_for_ticket(a)


if __name__ == '__main__':
        # run as script
        tickets = find_due_tickets_with_date(pott, date)
        for t in tickets:
                send_due_mail_for_ticket(t)

# eof.

Dieses Script muss einfach jeden Tag ausgeführt werden (dank XML-RPC ist der Linux-Benutzer egal).

Das dazugehörige Genshi-Template sieht so aus:

Dieses Ticket ist am $ticket.due_date fällig geworden!

$ticket.description

Zum Ticket gehen: $ticket.url
oder auf diese Mail antworten zum Kommentieren.

--
Dies ist eine automatische E-Mail vom PhysikOnline POTT
(Naechtlicher Due Date Cronjob)
$trac_url
Last modified 4 years ago Last modified on Apr 28, 2014 2:07:06 PM

Attachments (1)

Download all attachments as: .zip