From 65292bb7130ea4735ef4da2ee6eb9a12fb2929e3 Mon Sep 17 00:00:00 2001 From: Christopher Evans Date: Fri, 15 Nov 2019 15:40:58 +0000 Subject: [PATCH] Better summary and impact updates (#189) * Better summary and impact updates Prior to this change, the summary and impact commands would overwrite whatever was currently set for the incident. In most cases, our understanding of these develops over time, so we'd like to be update and refine what we've previously set. This change introduces actions and dialogs to update the summary and impact from what was previously set, making it easier for us to develop meaningful and thorough statements over time. --- response/slack/incident_commands/__init__.py | 3 + response/slack/incident_commands/impact.py | 106 +++++++++++++++++ .../incident_commands.py | 16 --- response/slack/incident_commands/summary.py | 112 ++++++++++++++++++ 4 files changed, 221 insertions(+), 16 deletions(-) create mode 100644 response/slack/incident_commands/__init__.py create mode 100644 response/slack/incident_commands/impact.py rename response/slack/{ => incident_commands}/incident_commands.py (88%) create mode 100644 response/slack/incident_commands/summary.py diff --git a/response/slack/incident_commands/__init__.py b/response/slack/incident_commands/__init__.py new file mode 100644 index 00000000..735d7c9d --- /dev/null +++ b/response/slack/incident_commands/__init__.py @@ -0,0 +1,3 @@ +from . import impact, incident_commands, summary + +__all__ = (impact, incident_commands, summary) diff --git a/response/slack/incident_commands/impact.py b/response/slack/incident_commands/impact.py new file mode 100644 index 00000000..8d123ec8 --- /dev/null +++ b/response/slack/incident_commands/impact.py @@ -0,0 +1,106 @@ +import json +import logging + +from response.core.models import Incident +from response.slack import block_kit, dialog_builder +from response.slack.decorators import ActionContext, action_handler, dialog_handler +from response.slack.decorators.incident_command import __default_incident_command +from response.slack.models import CommsChannel + +logger = logging.getLogger(__name__) + +UPDATE_CURRENT_IMPACT_ACTION = "update-current-impact-action" +SET_NEW_IMPACT_ACTION = "set-new-impact-action" +PROPOSED_MESSAGE_BLOCK_ID = "proposed" +UPDATE_IMPACT_DIALOG = "update-impact-dialog" + +NO_IMPACT_TEXT = "The impact of this incident hasn't been set yet." +CURRENT_TITLE = "*This is the current impact:*\n" +PROPOSED_TITLE = "*Or would you like to update the impact to this?*\n" +IMPACT_UPDATED_TITLE = "*The impact has been updated to:*\n" +CHANGE_BUTTON_TEXT = "Change" +ACCEPT_PROPOSED_TEXT = "Yes" + + +@__default_incident_command(["impact"], helptext="Explain the impact of this") +def update_impact(incident: Incident, user_id: str, message: str): + # Easy case. No impact currently and one has been provided + if message and not incident.impact: + incident.impact = message + incident.save() + return True, f"{IMPACT_UPDATED_TITLE}{message}" + + # Either no new impact has been provided, or one already exists + msg = block_kit.Message() + msg.add_block( + block_kit.Section( + block_id="update", + text=block_kit.Text(f"{CURRENT_TITLE}{incident.impact or NO_IMPACT_TEXT}"), + accessory=block_kit.Button( + CHANGE_BUTTON_TEXT, UPDATE_CURRENT_IMPACT_ACTION, value=incident.pk + ), + ) + ) + + # if the user has supplied a message, provide the option for them to set it without + # retyping in the dialog + if message: + msg.add_block( + block_kit.Section( + block_id=PROPOSED_MESSAGE_BLOCK_ID, + text=block_kit.Text(f"{PROPOSED_TITLE}{message}"), + accessory=block_kit.Button( + ACCEPT_PROPOSED_TEXT, SET_NEW_IMPACT_ACTION, value=incident.pk + ), + ) + ) + + comms_channel = CommsChannel.objects.get(incident=incident) + msg.send(comms_channel.channel_id) + return True, None + + +@action_handler(SET_NEW_IMPACT_ACTION) +def handle_set_new_impact(action_context: ActionContext): + for block in action_context.message["blocks"]: + print("Looking at block", block) + if block["block_id"] == PROPOSED_MESSAGE_BLOCK_ID: + impact = block["text"]["text"].replace(PROPOSED_TITLE, "") + action_context.incident.impact = impact + action_context.incident.save() + + comms_channel = CommsChannel.objects.get(incident=action_context.incident) + comms_channel.post_in_channel(f"{IMPACT_UPDATED_TITLE}{impact}") + return + + +@action_handler(UPDATE_CURRENT_IMPACT_ACTION) +def handle_open_impact_dialog(action_context: ActionContext): + dialog = dialog_builder.Dialog( + title="Update Impact", + submit_label="Update", + state=action_context.incident.pk, + elements=[ + dialog_builder.TextArea( + label="Impact", + name="impact", + optional=False, + value=action_context.incident.impact, + ) + ], + ) + + dialog.send_open_dialog(UPDATE_IMPACT_DIALOG, action_context.trigger_id) + + +@dialog_handler(UPDATE_IMPACT_DIALOG) +def update_status_page( + user_id: str, channel_id: str, submission: json, response_url: str, state: json +): + incident_id = state + incident = Incident.objects.get(pk=incident_id) + incident.impact = submission["impact"] + incident.save() + + comms_channel = CommsChannel.objects.get(incident=incident) + comms_channel.post_in_channel(f'{IMPACT_UPDATED_TITLE}{submission["impact"]}') diff --git a/response/slack/incident_commands.py b/response/slack/incident_commands/incident_commands.py similarity index 88% rename from response/slack/incident_commands.py rename to response/slack/incident_commands/incident_commands.py index 0991b39e..b5d0e4be 100644 --- a/response/slack/incident_commands.py +++ b/response/slack/incident_commands/incident_commands.py @@ -19,22 +19,6 @@ def send_help_text(incident: Incident, user_id: str, message: str): return True, get_help() -@__default_incident_command( - ["summary"], helptext="Provide a summary of what's going on" -) -def update_summary(incident: Incident, user_id: str, message: str): - incident.summary = message - incident.save() - return True, None - - -@__default_incident_command(["impact"], helptext="Explain the impact of this") -def update_impact(incident: Incident, user_id: str, message: str): - incident.impact = message - incident.save() - return True, None - - @__default_incident_command(["lead"], helptext="Assign someone as the incident lead") def set_incident_lead(incident: Incident, user_id: str, message: str): assignee = reference_to_id(message) or user_id diff --git a/response/slack/incident_commands/summary.py b/response/slack/incident_commands/summary.py new file mode 100644 index 00000000..28388739 --- /dev/null +++ b/response/slack/incident_commands/summary.py @@ -0,0 +1,112 @@ +import json +import logging + +from response.core.models import Incident +from response.slack import block_kit, dialog_builder +from response.slack.decorators import ActionContext, action_handler, dialog_handler +from response.slack.decorators.incident_command import __default_incident_command +from response.slack.models import CommsChannel + +logger = logging.getLogger(__name__) + +UPDATE_CURRENT_SUMMARY_ACTION = "update-current-summary-action" +SET_NEW_SUMMARY_ACTION = "set-new-summary-action" +PROPOSED_MESSAGE_BLOCK_ID = "proposed" +UPDATE_SUMMARY_DIALOG = "update-summary-dialog" + +NO_SUMMARY_TEXT = "This incident doesn't have a summary yet." +CURRENT_TITLE = "*This is the current summary:*\n" +PROPOSED_TITLE = "*Or would you like to update the summary to this?*\n" +SUMMARY_UPDATED_TITLE = "*The summary has been updated to:*\n" +CHANGE_BUTTON_TEXT = "Change" +ACCEPT_PROPOSED_TEXT = "Yes" + + +@__default_incident_command( + ["summary"], helptext="Provide a summary of what's going on" +) +def update_summary(incident: Incident, user_id: str, message: str): + # Easy case. No summary currently and one has been provided + if message and not incident.summary: + incident.summary = message + incident.save() + return True, f"{SUMMARY_UPDATED_TITLE}{message}" + + # Either no new summary has been provided, or one already exists + msg = block_kit.Message() + + msg.add_block( + block_kit.Section( + block_id="update", + text=block_kit.Text( + f"{CURRENT_TITLE}{incident.summary or NO_SUMMARY_TEXT}" + ), + accessory=block_kit.Button( + CHANGE_BUTTON_TEXT, UPDATE_CURRENT_SUMMARY_ACTION, value=incident.pk + ), + ) + ) + + # if the user has supplied a message, provide the option for them to set it without + # retyping in the dialog + if message: + msg.add_block(block_kit.Divider()) + msg.add_block( + block_kit.Section( + block_id=PROPOSED_MESSAGE_BLOCK_ID, + text=block_kit.Text(f"{PROPOSED_TITLE}{message}"), + accessory=block_kit.Button( + ACCEPT_PROPOSED_TEXT, SET_NEW_SUMMARY_ACTION, value=incident.pk + ), + ) + ) + + comms_channel = CommsChannel.objects.get(incident=incident) + msg.send(comms_channel.channel_id) + return True, None + + +@action_handler(SET_NEW_SUMMARY_ACTION) +def handle_set_new_summary(action_context: ActionContext): + for block in action_context.message["blocks"]: + print("Looking at block", block) + if block["block_id"] == PROPOSED_MESSAGE_BLOCK_ID: + summary = block["text"]["text"].replace(PROPOSED_TITLE, "") + action_context.incident.summary = summary + action_context.incident.save() + + comms_channel = CommsChannel.objects.get(incident=action_context.incident) + comms_channel.post_in_channel(f"{SUMMARY_UPDATED_TITLE}{summary}") + return + + +@action_handler(UPDATE_CURRENT_SUMMARY_ACTION) +def handle_open_summary_dialog(action_context: ActionContext): + dialog = dialog_builder.Dialog( + title="Update Summary", + submit_label="Update", + state=action_context.incident.pk, + elements=[ + dialog_builder.TextArea( + label="Summary", + name="summary", + optional=False, + value=action_context.incident.summary, + ) + ], + ) + + dialog.send_open_dialog(UPDATE_SUMMARY_DIALOG, action_context.trigger_id) + + +@dialog_handler(UPDATE_SUMMARY_DIALOG) +def update_status_page( + user_id: str, channel_id: str, submission: json, response_url: str, state: json +): + incident_id = state + incident = Incident.objects.get(pk=incident_id) + incident.summary = submission["summary"] + incident.save() + + comms_channel = CommsChannel.objects.get(incident=incident) + comms_channel.post_in_channel(f'{SUMMARY_UPDATED_TITLE}{submission["summary"]}')