Skip to content

Commit

Permalink
Populate headline post actions with @headline_post_action (#92)
Browse files Browse the repository at this point in the history
* Populate headline post actions with @headline_post_action

Adds a decorator that allows easily populating actions in headline
posts, so that we can add custom integrations there (e.g. PagerDuty).

Also adds a post_to_thread method in HeadlinePost

* Add docs + logging for @headline_post_action
  • Loading branch information
milesbxf authored Aug 9, 2019
1 parent 753e663 commit afd4aef
Show file tree
Hide file tree
Showing 9 changed files with 104 additions and 12 deletions.
30 changes: 29 additions & 1 deletion docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,32 @@ def handle_close_incident(action_context: ActionContext):
incident = action_context.incident
incident.end_time = datetime.now()
incident.save()
```
```

### Headline Post Actions

When a new incident is created, we create a "headline post" message in the central incidents channel. We add action buttons here - by default allowing creating an incident channel, editing the incident or closing. There is a decorator that allows you to add custom action buttons here - `@headline_post_action`.

For example:
```
from response.slack import block_kit
from response.slack.decorators import headline_post_action
@headline_post_action(order=150)
def my_cool_headline_action(headline_post):
return block_kit.Button(":sparkles: My cool action", "my-cool-action", value=headline_post.incident.pk)
```

You should provide a function which takes a `headline_post` parameter, and either returns `None` (a button won't be created) or an action, e.g. `block_kit.Button`. You can also specify an `order` parameter, which will determine the order in which actions appear. The default buttons have orders of 100, 200 and 300, so the above example would appear second:

![](https://www.dropbox.com/s/ummtglal8xmw2rj/Screenshot%202019-08-08%2014.55.20.png?raw=1)

Check the logs to see which actions have been registered, and what order they're registered in.

```
INFO - headline_post_a - Registering headline post action create_comms_channel_action with order 100
INFO - headline_post_a - Registering headline post action edit_incident_button with order 200
INFO - headline_post_a - Registering headline post action close_incident_button with order 300
INFO - headline_post_a - Registering headline post action my_cool_headline_action with order 150
```

1 change: 1 addition & 0 deletions response/slack/decorators/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from .action_handler import *
from .event_handler import *
from .headline_post_action import *
from .incident_command import *
from .keyword_handler import *
from .incident_notification import *
Expand Down
2 changes: 1 addition & 1 deletion response/slack/decorators/action_handler.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import after_response

from response.core.models.incident import Incident
from response.slack.models import CommsChannel
from response.slack.models.comms_channel import CommsChannel

import logging
logger = logging.getLogger(__name__)
Expand Down
2 changes: 1 addition & 1 deletion response/slack/decorators/event_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned

from response.core.models.incident import Incident
from response.slack.models import CommsChannel
from response.slack.models.comms_channel import CommsChannel


logger = logging.getLogger(__name__)
Expand Down
38 changes: 38 additions & 0 deletions response/slack/decorators/headline_post_action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import logging

logger = logging.getLogger(__name__)


SLACK_HEADLINE_POST_ACTION_MAPPINGS = {}


def headline_post_action(order=0, func=None):
"""headline_post_action
Decorator that allows adding a button/action into the headline post. Should be attached
to functions that take a HeadlinePost and return a Slack Action type (e.g. Button).
Actions can be conditional - returning None means that no Action will be added.
The order parameter determines the relative position the action appears in the post -
actions will be ordered low-high from left to right. Actions with the same order number
will be added in the order the decorator is executed, which depends on the order of file imports.
Usage:
@headline_post_action(100)
def edit_incident(headline_post):
return slack.block_kit.Button(":pencil2: Edit", EDIT_INCIDENT_BUTTON, value=headline_post.incident.pk)
"""

def _wrapper(fn):
SLACK_HEADLINE_POST_ACTION_MAPPINGS[
order
] = SLACK_HEADLINE_POST_ACTION_MAPPINGS.get(order, []) + [fn]
logger.info(f"Registering headline post action {fn.__name__} with order {order}")
return fn

if func:
return _wrapper(func)

return _wrapper
2 changes: 1 addition & 1 deletion response/slack/decorators/incident_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
import logging

from response.core.models import Incident
from response.slack.models import CommsChannel
from response.slack.models.comms_channel import CommsChannel
from response.slack.client import SlackError

logger = logging.getLogger(__name__)
Expand Down
3 changes: 2 additions & 1 deletion response/slack/decorators/incident_notification.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
from datetime import datetime

from response.core.models import Incident
from response.slack.models import CommsChannel, Notification
from response.slack.models.comms_channel import CommsChannel
from response.slack.models.notification import Notification

logger = logging.getLogger(__name__)

Expand Down
2 changes: 1 addition & 1 deletion response/slack/decorators/keyword_handler.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from response.core.models.incident import Incident
from response.slack.models import CommsChannel
from response.slack.models.comms_channel import CommsChannel

import logging
logger = logging.getLogger(__name__)
Expand Down
36 changes: 30 additions & 6 deletions response/slack/models/headline_post.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

from response import errors
from response.core.models.incident import Incident
from response.slack.decorators.headline_post_action import SLACK_HEADLINE_POST_ACTION_MAPPINGS, headline_post_action
from response.slack.models.comms_channel import CommsChannel

from response.slack.block_kit import *
Expand Down Expand Up @@ -71,12 +72,13 @@ def update_in_slack(self):
msg.add_block(Section(text=Text("Need something else?")))
actions = Actions(block_id="actions")

if not self.comms_channel:
actions.add_element(Button(":speaking_head_in_silhouette: Create Comms Channel", self.CREATE_COMMS_CHANNEL_BUTTON, value=self.incident.pk))

actions.add_element(Button(":pencil2: Edit", self.EDIT_INCIDENT_BUTTON, value=self.incident.pk))

actions.add_element(Button(":white_check_mark: Close", self.CLOSE_INCIDENT_BUTTON, value=self.incident.pk))
# Add all actions mapped by @headline_post_action decorators
for key in sorted(SLACK_HEADLINE_POST_ACTION_MAPPINGS.keys()):
funclist = SLACK_HEADLINE_POST_ACTION_MAPPINGS[key]
for f in funclist:
action = f(self)
if action:
actions.add_element(action)

msg.add_block(actions)

Expand All @@ -89,3 +91,25 @@ def update_in_slack(self):
if not self.message_ts:
self.message_ts = response['ts']
self.save()

def post_to_thread(self, message):
settings.SLACK_CLIENT.send_message(settings.INCIDENT_CHANNEL_ID, message,
thread_ts=self.message_ts)

# Default/core actions to display on headline post. In order to allow inserting actions between these ones we increment the order by 100

@headline_post_action(order=100)
def create_comms_channel_action(headline_post):
if headline_post.comms_channel:
# No need to create an action, channel already exists
return None
return Button(":speaking_head_in_silhouette: Create Comms Channel", HeadlinePost.CREATE_COMMS_CHANNEL_BUTTON, value=headline_post.incident.pk)

@headline_post_action(order=200)
def edit_incident_button(headline_post):
return Button(":pencil2: Edit", HeadlinePost.EDIT_INCIDENT_BUTTON, value=headline_post.incident.pk)

@headline_post_action(order=300)
def close_incident_button(headline_post):
return Button(":white_check_mark: Close", HeadlinePost.CLOSE_INCIDENT_BUTTON, value=headline_post.incident.pk)

0 comments on commit afd4aef

Please sign in to comment.