Browse Source

DOCUMENT EVERYTHING.

tags/v0.1^2
Ben Kurtovic 12 years ago
parent
commit
a231e44f2e
1 changed files with 65 additions and 6 deletions
  1. +65
    -6
      earwigbot/tasks/drn_clerkbot.py

+ 65
- 6
earwigbot/tasks/drn_clerkbot.py View File

@@ -310,12 +310,22 @@ class DRNClerkBot(Task):
return notices return notices


def clerk_new_case(self, case, volunteers, signatures): def clerk_new_case(self, case, volunteers, signatures):
"""Clerk a case in the "brand new" state.

The case will be set to "open" if a volunteer edits it.
"""
notices = self.notify_parties(case) notices = self.notify_parties(case)
if any([editor in volunteers for (editor, timestamp) in signatures]): if any([editor in volunteers for (editor, timestamp) in signatures]):
self.update_status(case, self.STATUS_OPEN) self.update_status(case, self.STATUS_OPEN)
return notices return notices


def clerk_open_case(self, case, signatures): def clerk_open_case(self, case, signatures):
"""Clerk an open case (has been edited by a reviewer).

The case will be set to "needassist" if 15,000 bytes have been added
since a volunteer last edited, "stale" if no edits have occured in two
days, or "review" if it has been open for over four days.
"""
if self.check_for_review(case): if self.check_for_review(case):
return [] return []
if len(case.body) - case.last_volunteer_size > 15000: if len(case.body) - case.last_volunteer_size > 15000:
@@ -327,6 +337,11 @@ class DRNClerkBot(Task):
return [] return []


def clerk_needassist_case(self, case, volunteers, newsigs): def clerk_needassist_case(self, case, volunteers, newsigs):
"""Clerk a "needassist" case (no volunteer edits in 15,000 bytes).

The case will be set to "open" if a volunteer edits, or "review" if it
has been open for over four days.
"""
if self.check_for_review(case): if self.check_for_review(case):
return [] return []
if any([editor in volunteers for (editor, timestamp) in newsigs]): if any([editor in volunteers for (editor, timestamp) in newsigs]):
@@ -334,6 +349,11 @@ class DRNClerkBot(Task):
return [] return []


def clerk_stale_case(self, case, newsigs): def clerk_stale_case(self, case, newsigs):
"""Clerk a stale case (no edits in two days).

The case will be set to "open" if anyone edits, or "review" if it has
been open for over four days.
"""
if self.check_for_review(case): if self.check_for_review(case):
return [] return []
if newsigs: if newsigs:
@@ -341,6 +361,12 @@ class DRNClerkBot(Task):
return [] return []


def clerk_review_case(self, case): def clerk_review_case(self, case):
"""Clerk a "review" case (open for more than four days).

A message will be set to the "very old notifiee", which is generally
[[User talk:Szhang (WMF)]], if the case has been open for more than
seven days.
"""
age = (datetime.utcnow() - case.file_time).total_seconds() age = (datetime.utcnow() - case.file_time).total_seconds()
if age > 60 * 60 * 24 * 7: if age > 60 * 60 * 24 * 7:
if not case.very_old_notified: if not case.very_old_notified:
@@ -355,6 +381,13 @@ class DRNClerkBot(Task):
return [] return []


def clerk_closed_case(self, case, signatures): def clerk_closed_case(self, case, signatures):
"""Clerk a closed or resolved case.

The case will be archived if it has been closed/resolved for more than
one day and no edits have been made in the meantime. "Archiving" is
the process of adding {{DRN archive top}}, {{DRN archive bottom}}, and
removing the [[User:DoNotArchiveUntil]] comment.
"""
if case.close_time == self.min_ts: if case.close_time == self.min_ts:
case.close_time = datetime.utcnow() case.close_time = datetime.utcnow()
timestamps = [timestamp for (editor, timestamp) in signatures] timestamps = [timestamp for (editor, timestamp) in signatures]
@@ -373,18 +406,26 @@ class DRNClerkBot(Task):
self.logger.debug(u" {0}: archived case".format(case.id)) self.logger.debug(u" {0}: archived case".format(case.id))


def clerk_unknown_case(self, conn, case): def clerk_unknown_case(self, conn, case):
"""Clerk a case with "unknown" status.

This will generally be either closed, archived, and off the page, or
with a status we can't identify. We won't do anything to the case other
than update it in the database.
"""
if case.new: if case.new:
self.save_new_case(conn, case) self.save_new_case(conn, case)
else: else:
self.save_existing_case(conn, case) self.save_existing_case(conn, case)


def check_for_review(self, case): def check_for_review(self, case):
"""Check whether a case is old enough to be set to "review"."""
age = (datetime.utcnow() - case.file_time).total_seconds() age = (datetime.utcnow() - case.file_time).total_seconds()
if age > 60 * 60 * 24 * 4: if age > 60 * 60 * 24 * 4:
return self.update_status(case, self.STATUS_REVIEW) return self.update_status(case, self.STATUS_REVIEW)
return False return False


def update_status(self, case, new): def update_status(self, case, new):
"""Safely update the status of a case, so we don't edit war."""
old_n = self.ALIASES[case.status][0].upper() old_n = self.ALIASES[case.status][0].upper()
new_n = self.ALIASES[new][0].upper() new_n = self.ALIASES[new][0].upper()
old_n = "NEW" if not old_n else old_n old_n = "NEW" if not old_n else old_n
@@ -399,6 +440,10 @@ class DRNClerkBot(Task):
return False return False


def read_signatures(self, text): def read_signatures(self, text):
"""Return a list of all parseable signatures in the body of a case.

Signatures are returned as tuples of (editor, timestamp as datetime).
"""
regex = r"\[\[(?:User(?:\stalk)?\:|Special\:Contributions\/)" regex = r"\[\[(?:User(?:\stalk)?\:|Special\:Contributions\/)"
regex += r"([^\n\[\]|]{,256}?)(?:\||\]\])" regex += r"([^\n\[\]|]{,256}?)(?:\||\]\])"
regex += r"(?!.*?(?:User(?:\stalk)?\:|Special\:Contributions\/).*?)" regex += r"(?!.*?(?:User(?:\stalk)?\:|Special\:Contributions\/).*?)"
@@ -415,12 +460,17 @@ class DRNClerkBot(Task):
return signatures return signatures


def get_signatures_from_db(self, conn, case): def get_signatures_from_db(self, conn, case):
"""Return a list of signatures in a case from the database.

The return type is the same as read_signatures().
"""
query = "SELECT signature_username, signature_timestamp FROM signatures WHERE signature_case = ?" query = "SELECT signature_username, signature_timestamp FROM signatures WHERE signature_case = ?"
with conn.cursor() as cursor: with conn.cursor() as cursor:
cursor.execute(query, (case.id,)) cursor.execute(query, (case.id,))
return cursor.fetchall() return cursor.fetchall()


def notify_parties(self, case): def notify_parties(self, case):
"""Schedule notices to be sent to all parties of a case."""
if case.parties_notified: if case.parties_notified:
return [] return []


@@ -444,13 +494,15 @@ class DRNClerkBot(Task):
return notices return notices


def save_case_updates(self, conn, case, volunteers, sigs, storedsigs): def save_case_updates(self, conn, case, volunteers, sigs, storedsigs):
"""Save any updates made to a case and signatures in the database."""
if case.status != case.original_status: if case.status != case.original_status:
case.last_action = case.status
new = self.ALIASES[case.status][0]
tl_status_esc = re.escape(self.tl_status)
search = "\{\{" + tl_status_esc + "(\|?.*?)\}\}"
repl = "{{" + self.tl_status + "|" + new + "}}"
case.body = re.sub(search, repl, case.body)
if case.status != self.STATUS_UNKNOWN:
case.last_action = case.status
new = self.ALIASES[case.status][0]
tl_status_esc = re.escape(self.tl_status)
search = "\{\{" + tl_status_esc + "(\|?.*?)\}\}"
repl = "{{" + self.tl_status + "|" + new + "}}"
case.body = re.sub(search, repl, case.body)


newest_ts = max([stamp for (user, stamp) in sigs]) newest_ts = max([stamp for (user, stamp) in sigs])
newest_user = [usr for (usr, stamp) in sigs if stamp == newest_ts][0] newest_user = [usr for (usr, stamp) in sigs if stamp == newest_ts][0]
@@ -485,6 +537,7 @@ class DRNClerkBot(Task):
self.logger.debug(log) self.logger.debug(log)


def save_new_case(self, conn, case): def save_new_case(self, conn, case):
"""Save a brand new case to the database."""
args = (case.id, case.title, case.status, case.last_action, args = (case.id, case.title, case.status, case.last_action,
case.file_user, case.file_time, case.modify_user, case.file_user, case.file_time, case.modify_user,
case.modify_time, case.volunteer_user, case.volunteer_time, case.modify_time, case.volunteer_user, case.volunteer_time,
@@ -497,6 +550,7 @@ class DRNClerkBot(Task):
self.logger.debug(log) self.logger.debug(log)


def save_existing_case(self, conn, case): def save_existing_case(self, conn, case):
"""Save an existing case to the database, updating as necessary."""
with conn.cursor(oursql.DictCursor) as cursor: with conn.cursor(oursql.DictCursor) as cursor:
query = "SELECT * FROM cases WHERE case_id = ?" query = "SELECT * FROM cases WHERE case_id = ?"
cursor.execute(query, (case.id,)) cursor.execute(query, (case.id,))
@@ -593,6 +647,7 @@ class DRNClerkBot(Task):
self.logger.debug("Done sending notices") self.logger.debug("Done sending notices")


def update_chart(self, conn, site): def update_chart(self, conn, site):
"""Update the chart of open or recently closed cases."""
page = site.get_page(self.chart_title) page = site.get_page(self.chart_title)
self.logger.info(u"Updating case status at [[{0}]]".format(page.title)) self.logger.info(u"Updating case status at [[{0}]]".format(page.title))
statuses = self.compile_chart(conn) statuses = self.compile_chart(conn)
@@ -611,6 +666,7 @@ class DRNClerkBot(Task):
self.logger.info(u"Chart saved to [[{0}]]".format(page.title)) self.logger.info(u"Chart saved to [[{0}]]".format(page.title))


def compile_chart(self, conn): def compile_chart(self, conn):
"""Actually generate the chart from the database."""
chart = "{{" + self.tl_chart_header + "}}\n" chart = "{{" + self.tl_chart_header + "}}\n"
query = "SELECT * FROM cases" query = "SELECT * FROM cases"
with conn.cursor(oursql.DictCursor) as cursor: with conn.cursor(oursql.DictCursor) as cursor:
@@ -622,6 +678,7 @@ class DRNClerkBot(Task):
return chart return chart


def compile_row(self, case): def compile_row(self, case):
"""Generate a single row of the chart from a dict via the database."""
data = "|t={title}|s={case_status}" data = "|t={title}|s={case_status}"
data += "|cu={case_file_user}|cs={file_sortkey}|ct={file_time}" data += "|cu={case_file_user}|cs={file_sortkey}|ct={file_time}"
if case["case_volunteer_user"]: if case["case_volunteer_user"]:
@@ -644,6 +701,7 @@ class DRNClerkBot(Task):
return dt.strftime("%H:%M, %d %b %Y") return dt.strftime("%H:%M, %d %b %Y")


def format_time_since(self, dt): def format_time_since(self, dt):
"""Return a string telling the time since datetime occured."""
parts = [("year", 31536000), ("day", 86400), ("hour", 3600)] parts = [("year", 31536000), ("day", 86400), ("hour", 3600)]
seconds = int((datetime.utcnow() - dt).total_seconds()) seconds = int((datetime.utcnow() - dt).total_seconds())
msg = [] msg = []
@@ -656,6 +714,7 @@ class DRNClerkBot(Task):
return ", ".join(msg) + " ago" if msg else "0 hours ago" return ", ".join(msg) + " ago" if msg else "0 hours ago"


def purge_old_data(self, conn): def purge_old_data(self, conn):
"""Delete old cases (> one month) from the database."""
log = "Purging closed cases older than a month from the database" log = "Purging closed cases older than a month from the database"
self.logger.info(log) self.logger.info(log)
query = """DELETE cases, signatures query = """DELETE cases, signatures


Loading…
Cancel
Save