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"]}')