Files
zulip/zerver/webhooks/taiga/view.py
sameerchoubey 996caddb11 webhooks/taiga: Stop using emojis in notifications.
This fixes the fact that these emoji were sometimes not displaying
properly (because of changes in the emoji names used in the codebase),
while also making this integration more standard (since it was the
only one with such an aggressive use of emoji).
2018-11-06 12:06:49 -08:00

273 lines
12 KiB
Python

"""Taiga integration for Zulip.
Tips for notification output:
*Text formatting*: if there has been a change of a property, the new
value should always be in bold; otherwise the subject of US/task
should be in bold.
"""
from typing import Any, Dict, List, Mapping, Optional, Tuple
import ujson
from django.http import HttpRequest, HttpResponse
from django.utils.translation import ugettext as _
from zerver.decorator import api_key_only_webhook_view
from zerver.lib.request import REQ, has_request_variables
from zerver.lib.response import json_error, json_success
from zerver.lib.webhooks.common import check_send_webhook_message
from zerver.models import UserProfile
@api_key_only_webhook_view('Taiga')
@has_request_variables
def api_taiga_webhook(request: HttpRequest, user_profile: UserProfile,
message: Dict[str, Any]=REQ(argument_type='body')) -> HttpResponse:
parsed_events = parse_message(message)
content_lines = []
for event in parsed_events:
content_lines.append(generate_content(event) + '\n')
content = "".join(sorted(content_lines))
topic = 'General'
check_send_webhook_message(request, user_profile, topic, content)
return json_success()
templates = {
'epic': {
'create': u'%(user)s created epic **%(subject)s**',
'set_assigned_to': u'%(user)s assigned epic **%(subject)s** to %(new)s.',
'unset_assigned_to': u'%(user)s unassigned epic **%(subject)s**',
'changed_assigned_to': u'%(user)s reassigned epic **%(subject)s**'
' from %(old)s to %(new)s.',
'blocked': u'%(user)s blocked epic **%(subject)s**',
'unblocked': u'%(user)s unblocked epic **%(subject)s**',
'changed_status': u'%(user)s changed status of epic **%(subject)s**'
' from %(old)s to %(new)s.',
'renamed': u'%(user)s renamed epic from **%(old)s** to **%(new)s**',
'description_diff': u'%(user)s updated description of epic **%(subject)s**',
'commented': u'%(user)s commented on epic **%(subject)s**',
'delete': u'%(user)s deleted epic **%(subject)s**',
},
'relateduserstory': {
'create': (u'%(user)s added a related user story '
u'**%(userstory_subject)s** to the epic **%(epic_subject)s**'),
'delete': (u'%(user)s removed a related user story ' +
u'**%(userstory_subject)s** from the epic **%(epic_subject)s**'),
},
'userstory': {
'create': u'%(user)s created user story **%(subject)s**.',
'set_assigned_to': u'%(user)s assigned user story **%(subject)s** to %(new)s.',
'unset_assigned_to': u'%(user)s unassigned user story **%(subject)s**.',
'changed_assigned_to': u'%(user)s reassigned user story **%(subject)s**'
' from %(old)s to %(new)s.',
'points': u'%(user)s changed estimation of user story **%(subject)s**.',
'blocked': u'%(user)s blocked user story **%(subject)s**.',
'unblocked': u'%(user)s unblocked user story **%(subject)s**.',
'set_milestone': u'%(user)s added user story **%(subject)s** to sprint %(new)s.',
'unset_milestone': u'%(user)s removed user story **%(subject)s** from sprint %(old)s.',
'changed_milestone': u'%(user)s changed sprint of user story **%(subject)s** from %(old)s'
' to %(new)s.',
'changed_status': u'%(user)s changed status of user story **%(subject)s**'
' from %(old)s to %(new)s.',
'closed': u'%(user)s closed user story **%(subject)s**.',
'reopened': u'%(user)s reopened user story **%(subject)s**.',
'renamed': u'%(user)s renamed user story from %(old)s to **%(new)s**.',
'description_diff': u'%(user)s updated description of user story **%(subject)s**.',
'commented': u'%(user)s commented on user story **%(subject)s**.',
'delete': u'%(user)s deleted user story **%(subject)s**.'
},
'milestone': {
'create': u'%(user)s created sprint **%(subject)s**.',
'renamed': u'%(user)s renamed sprint from %(old)s to **%(new)s**.',
'estimated_start': u'%(user)s changed estimated start of sprint **%(subject)s**'
' from %(old)s to %(new)s.',
'estimated_finish': u'%(user)s changed estimated finish of sprint **%(subject)s**'
' from %(old)s to %(new)s.',
'delete': u'%(user)s deleted sprint **%(subject)s**.'
},
'task': {
'create': u'%(user)s created task **%(subject)s**.',
'set_assigned_to': u'%(user)s assigned task **%(subject)s** to %(new)s.',
'unset_assigned_to': u'%(user)s unassigned task **%(subject)s**.',
'changed_assigned_to': u'%(user)s reassigned task **%(subject)s**'
' from %(old)s to %(new)s.',
'blocked': u'%(user)s blocked task **%(subject)s**.',
'unblocked': u'%(user)s unblocked task **%(subject)s**.',
'set_milestone': u'%(user)s added task **%(subject)s** to sprint %(new)s.',
'changed_milestone': u'%(user)s changed sprint of task '
'**%(subject)s** from %(old)s to %(new)s.',
'changed_status': u'%(user)s changed status of task **%(subject)s**'
' from %(old)s to %(new)s.',
'renamed': u'%(user)s renamed task %(old)s to **%(new)s**.',
'description_diff': u'%(user)s updated description of task **%(subject)s**.',
'commented': u'%(user)s commented on task **%(subject)s**.',
'delete': u'%(user)s deleted task **%(subject)s**.',
'changed_us': u'%(user)s moved task **%(subject)s** from user story %(old)s to %(new)s.'
},
'issue': {
'create': u'%(user)s created issue **%(subject)s**.',
'set_assigned_to': u'%(user)s assigned issue **%(subject)s** to %(new)s.',
'unset_assigned_to': u'%(user)s unassigned issue **%(subject)s**.',
'changed_assigned_to': u'%(user)s reassigned issue **%(subject)s**'
' from %(old)s to %(new)s.',
'changed_priority': u'%(user)s changed priority of issue '
'**%(subject)s** from %(old)s to %(new)s.',
'changed_severity': u'%(user)s changed severity of issue '
'**%(subject)s** from %(old)s to %(new)s.',
'changed_status': u'%(user)s changed status of issue **%(subject)s**'
' from %(old)s to %(new)s.',
'changed_type': u'%(user)s changed type of issue **%(subject)s** from %(old)s to %(new)s.',
'renamed': u'%(user)s renamed issue %(old)s to **%(new)s**.',
'description_diff': u'%(user)s updated description of issue **%(subject)s**.',
'commented': u'%(user)s commented on issue **%(subject)s**.',
'delete': u'%(user)s deleted issue **%(subject)s**.'
},
}
return_type = Tuple[Optional[Dict[str, Any]], Optional[Dict[str, Any]]]
def get_old_and_new_values(change_type: str,
message: Mapping[str, Any]) -> return_type:
""" Parses the payload and finds previous and current value of change_type."""
if change_type in ['subject', 'name', 'estimated_finish', 'estimated_start']:
old = message["change"]["diff"][change_type]["from"]
new = message["change"]["diff"][change_type]["to"]
return old, new
old = message["change"]["diff"][change_type].get("from")
new = message["change"]["diff"][change_type].get("to")
return old, new
def parse_comment(message: Mapping[str, Any]) -> Dict[str, Any]:
""" Parses the comment to issue, task or US. """
return {
'event': 'commented',
'type': message["type"],
'values': {
'user': get_owner_name(message),
'subject': get_subject(message)
}
}
def parse_create_or_delete(message: Mapping[str, Any]) -> Dict[str, Any]:
""" Parses create or delete event. """
if message["type"] == 'relateduserstory':
return {
'type': message["type"],
'event': message["action"],
'values': {
'user': get_owner_name(message),
'epic_subject': message['data']['epic']['subject'],
'userstory_subject': message['data']['user_story']['subject'],
}
}
return {
'type': message["type"],
'event': message["action"],
'values': {
'user': get_owner_name(message),
'subject': get_subject(message)
}
}
def parse_change_event(change_type: str, message: Mapping[str, Any]) -> Optional[Dict[str, Any]]:
""" Parses change event. """
evt = {} # type: Dict[str, Any]
values = {
'user': get_owner_name(message),
'subject': get_subject(message)
} # type: Dict[str, Any]
if change_type in ["description_diff", "points"]:
event_type = change_type
elif change_type in ["milestone", "assigned_to"]:
old, new = get_old_and_new_values(change_type, message)
if not old:
event_type = "set_" + change_type
values["new"] = new
elif not new:
event_type = "unset_" + change_type
values["old"] = old
else:
event_type = "changed_" + change_type
values.update({'old': old, 'new': new})
elif change_type == "is_blocked":
if message["change"]["diff"]["is_blocked"]["to"]:
event_type = "blocked"
else:
event_type = "unblocked"
elif change_type == "is_closed":
if message["change"]["diff"]["is_closed"]["to"]:
event_type = "closed"
else:
event_type = "reopened"
elif change_type == "user_story":
old, new = get_old_and_new_values(change_type, message)
event_type = "changed_us"
values.update({'old': old, 'new': new})
elif change_type in ["subject", 'name']:
event_type = 'renamed'
old, new = get_old_and_new_values(change_type, message)
values.update({'old': old, 'new': new})
elif change_type in ["estimated_finish", "estimated_start"]:
old, new = get_old_and_new_values(change_type, message)
if not old == new:
event_type = change_type
values.update({'old': old, 'new': new})
else:
# date hasn't changed
return None
elif change_type in ["priority", "severity", "type", "status"]:
event_type = 'changed_' + change_type
old, new = get_old_and_new_values(change_type, message)
values.update({'old': old, 'new': new})
else:
# we are not supporting this type of event
return None
evt.update({"type": message["type"], "event": event_type, "values": values})
return evt
def parse_message(message: Mapping[str, Any]) -> List[Dict[str, Any]]:
""" Parses the payload by delegating to specialized functions. """
events = []
if message["action"] in ['create', 'delete']:
events.append(parse_create_or_delete(message))
elif message["action"] == 'change':
if message["change"]["diff"]:
for value in message["change"]["diff"]:
parsed_event = parse_change_event(value, message)
if parsed_event:
events.append(parsed_event)
if message["change"]["comment"]:
events.append(parse_comment(message))
return events
def generate_content(data: Mapping[str, Any]) -> str:
""" Gets the template string and formats it with parsed data. """
return templates[data['type']][data['event']] % data['values']
def get_owner_name(message: Mapping[str, Any]) -> str:
return message["by"]["full_name"]
def get_subject(message: Mapping[str, Any]) -> str:
data = message["data"]
return data.get("subject", data.get("name"))