Add incident bot.
This is a pretty alpha proof-of-concept.
This commit is contained in:
parent
09deda9466
commit
4d03ab0558
0
zulip_bots/zulip_bots/bots/incident/__init__.py
Normal file
0
zulip_bots/zulip_bots/bots/incident/__init__.py
Normal file
144
zulip_bots/zulip_bots/bots/incident/incident.py
Normal file
144
zulip_bots/zulip_bots/bots/incident/incident.py
Normal file
|
@ -0,0 +1,144 @@
|
|||
import html
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
from zulip_bots.lib import Any, StateHandlerError
|
||||
|
||||
from typing import Optional, Any, Dict, Tuple
|
||||
|
||||
QUESTION = 'How should we handle this?'
|
||||
|
||||
ANSWERS = {
|
||||
'1': 'known issue',
|
||||
'2': 'ignore',
|
||||
'3': 'in process',
|
||||
'4': 'escalate',
|
||||
}
|
||||
|
||||
class InvalidAnswerException(Exception):
|
||||
pass
|
||||
|
||||
class IncidentHandler:
|
||||
def usage(self) -> str:
|
||||
return '''
|
||||
This plugin lets folks reports incidents and
|
||||
triage them. It is intended to be sample code.
|
||||
In the real world you'd modify this code to talk
|
||||
to some kind of issue tracking system. But the
|
||||
glue code here should be pretty portable.
|
||||
'''
|
||||
|
||||
def handle_message(self, message: Dict[str, Any], bot_handler: Any) -> None:
|
||||
query = message['content']
|
||||
if query.startswith('new '):
|
||||
start_new_incident(query, message, bot_handler)
|
||||
elif query.startswith('answer '):
|
||||
try:
|
||||
(ticket_id, answer) = parse_answer(query)
|
||||
except InvalidAnswerException:
|
||||
bot_response = 'Invalid answer format'
|
||||
bot_handler.send_reply(message, bot_response)
|
||||
return
|
||||
bot_response = 'Incident %s\n status = %s' % (ticket_id, answer)
|
||||
bot_handler.send_reply(message, bot_response)
|
||||
else:
|
||||
bot_response = 'type "new <description>" for a new incident'
|
||||
bot_handler.send_reply(message, bot_response)
|
||||
|
||||
def start_new_incident(query: str, message: Dict[str, Any], bot_handler: Any) -> None:
|
||||
# Here is where we would enter the incident in some sort of backend
|
||||
# system. We just simulate everything by having an incident id that
|
||||
# we generate here.
|
||||
|
||||
incident = query[len('new '):]
|
||||
|
||||
ticket_id = generate_ticket_id(bot_handler.storage)
|
||||
bot_response = format_incident_for_markdown(ticket_id, incident)
|
||||
widget_content = format_incident_for_widget(ticket_id, incident)
|
||||
|
||||
bot_handler.send_reply(message, bot_response, widget_content)
|
||||
|
||||
def parse_answer(query: str) -> Tuple[str, str]:
|
||||
m = re.match('answer\s+(TICKET....)\s+(.)', query)
|
||||
if not m:
|
||||
raise InvalidAnswerException()
|
||||
|
||||
ticket_id = m.group(1)
|
||||
|
||||
# In a real world system, we'd validate the ticket_id against
|
||||
# a backend system. (You could use Zulip itself to store incident
|
||||
# data, if you want something really lite, but there are plenty
|
||||
# of systems that specialize in incident management.)
|
||||
|
||||
answer = m.group(2).upper()
|
||||
if answer not in '1234':
|
||||
raise InvalidAnswerException()
|
||||
|
||||
return (ticket_id, ANSWERS[answer])
|
||||
|
||||
def generate_ticket_id(storage: Any) -> str:
|
||||
try:
|
||||
incident_num = storage.get('ticket_id')
|
||||
except (StateHandlerError, KeyError):
|
||||
incident_num = 0
|
||||
incident_num += 1
|
||||
incident_num = incident_num % (1000)
|
||||
storage.put('ticket_id', incident_num)
|
||||
ticket_id = 'TICKET%04d' % (incident_num,)
|
||||
return ticket_id
|
||||
|
||||
def format_incident_for_widget(ticket_id: str, incident: Dict[str, Any]) -> str:
|
||||
widget_type = 'zform'
|
||||
|
||||
heading = ticket_id + ': ' + incident
|
||||
|
||||
def get_choice(code: str) -> Dict[str, str]:
|
||||
answer = ANSWERS[code]
|
||||
reply = 'answer ' + ticket_id + ' ' + code
|
||||
|
||||
return dict(
|
||||
type='multiple_choice',
|
||||
short_name=code,
|
||||
long_name=answer,
|
||||
reply=reply,
|
||||
)
|
||||
|
||||
choices = [get_choice(code) for code in '1234']
|
||||
|
||||
extra_data = dict(
|
||||
type='choices',
|
||||
heading=heading,
|
||||
choices=choices,
|
||||
)
|
||||
|
||||
widget_content = dict(
|
||||
widget_type=widget_type,
|
||||
extra_data=extra_data,
|
||||
)
|
||||
payload = json.dumps(widget_content)
|
||||
return payload
|
||||
|
||||
def format_incident_for_markdown(ticket_id: str, incident: Dict[str, Any]) -> str:
|
||||
answer_list = '\n'.join([
|
||||
'* **{code}** {answer}'.format(
|
||||
code=code,
|
||||
answer=ANSWERS[code],
|
||||
)
|
||||
for code in '1234'
|
||||
])
|
||||
how_to_respond = '''**reply**: answer {ticket_id} <code>'''.format(ticket_id=ticket_id)
|
||||
|
||||
content = '''
|
||||
Incident: {incident}
|
||||
Q: {question}
|
||||
|
||||
{answer_list}
|
||||
{how_to_respond}'''.format(
|
||||
question=QUESTION,
|
||||
answer_list=answer_list,
|
||||
how_to_respond=how_to_respond,
|
||||
incident=incident,
|
||||
)
|
||||
return content
|
||||
|
||||
handler_class = IncidentHandler
|
Loading…
Reference in a new issue