Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Shift Swap Request Slack message formatting #2918

Merged
merged 10 commits into from
Sep 1, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Update Shift Swap Request Slack message formatting by @joeyorlando ([#2918](https://github.com/grafana/oncall/pull/2918))
- Performance and UX tweaks to integrations page ([#2869](https://github.com/grafana/oncall/pull/2869))
- Expand users details in filter swaps internal endpoint ([#2921](https://github.com/grafana/oncall/pull/2921))
- Truncate exported final shifts to match the requested period ([#2924](https://github.com/grafana/oncall/pull/2924))
Expand Down
10 changes: 5 additions & 5 deletions docs/sources/on-call-schedules/shift-swaps/_index.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,14 @@ of a schedule, or clicking the button shown when hovering on a particular shift
<img src="/static/img/oncall/swap-web-hover.png">
<img src="/static/img/oncall/swap-web-request.png">

>**Note**: no recurrence rules support is available when requesting a shift swap. If you need to recurrently change a shift,
consider creating a higher level layer rotation with the desired updates.
> **Note**: no recurrence rules support is available when requesting a shift swap. If you need to recurrently change a shift,
> consider creating a higher level layer rotation with the desired updates.

Upon submitting the request, a Slack notification will be sent to the channel associated to the correspondent
schedule, if there is one. A [mobile push notification][shift-swap-notifications] will be sent to team members who
participate in the schedule and have the notifications enabled.

<img src="/static/img/oncall/swap-slack-notification.png">
<img src="/static/img/oncall/swap-slack-notification-3.png">

Push notifications are sent 4 weeks ahead of the requested shift swap, or shortly after creation in case
the shift swap start time is less than 4 weeks away, but always during users' working hours (by default 9am-5pm on
Expand All @@ -64,8 +64,8 @@ The follow-up notifications will be sent at the following intervals before the s

You can delete the swap request at any time. If the swap has been taken, it will automatically be undone upon removal.

>**Note**: if [RBAC][rbac] is enabled, a user is required to have the `SCHEDULES_WRITE` permission to create,
update, take or delete a swap request. `SCHEDULES_READ` will be enough to get details about existing requests.
> **Note**: if [RBAC][rbac] is enabled, a user is required to have the `SCHEDULES_WRITE` permission to create,
> update, take or delete a swap request. `SCHEDULES_READ` will be enough to get details about existing requests.

## Check existing swap requests

Expand Down
70 changes: 32 additions & 38 deletions engine/apps/slack/scenarios/shift_swap_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import humanize
from django.utils import timezone

from apps.slack.constants import DIVIDER
from apps.slack.models import SlackMessage
from apps.slack.scenarios import scenario_step
from apps.slack.types import Block, BlockActionType, EventPayload, PayloadType, ScenarioRoute
Expand All @@ -21,17 +20,26 @@
SHIFT_SWAP_PK_ACTION_KEY = "shift_swap_request_pk"


def _schedule_slack_url(shift_swap_request) -> str:
schedule = shift_swap_request.schedule
return f"<{schedule.web_detail_page_link}|{schedule.name}>"


class BaseShiftSwapRequestStep(scenario_step.ScenarioStep):
def _generate_blocks(self, shift_swap_request: "ShiftSwapRequest") -> Block.AnyBlocks:
pk = shift_swap_request.pk

main_message_text = f"Your teammate {shift_swap_request.beneficiary.get_username_with_slack_verbal()} has submitted a shift swap request."
main_message_text = (
f"*New shift swap request for {_schedule_slack_url(shift_swap_request)}*\n"
f"Your teammate {shift_swap_request.beneficiary.get_username_with_slack_verbal()} has submitted a shift swap request."
)

datetime_format = SlackDateFormat.DATE_LONG_PRETTY
time_format = SlackDateFormat.TIME

shift_details = ""
for shift in shift_swap_request.shifts():
shifts = shift_swap_request.shifts()
for shift in shifts:
shift_start = shift["start"]
shift_start_posix = shift_start.timestamp()
shift_end = shift["end"]
Expand All @@ -58,18 +66,22 @@ def _generate_blocks(self, shift_swap_request: "ShiftSwapRequest") -> Block.AnyB
},
},
),
typing.cast(
Block.Section,
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*📅 Shift Details*:\n\n{shift_details}",
},
},
),
]

if shifts:
blocks.append(
typing.cast(
Block.Section,
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Shift detail{'s' if len(shifts) > 1 else ''}*\n{shift_details}",
},
},
),
)

if description := shift_swap_request.description:
blocks.append(
typing.cast(
Expand All @@ -78,7 +90,7 @@ def _generate_blocks(self, shift_swap_request: "ShiftSwapRequest") -> Block.AnyB
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*📝 Description*: {description}",
"text": f"*Description*\n{description}",
},
},
)
Expand All @@ -92,7 +104,7 @@ def _generate_blocks(self, shift_swap_request: "ShiftSwapRequest") -> Block.AnyB
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*Update*: this shift swap request has been deleted.",
"text": " this shift swap request has been deleted",
},
},
),
Expand All @@ -105,7 +117,7 @@ def _generate_blocks(self, shift_swap_request: "ShiftSwapRequest") -> Block.AnyB
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"*Update*: {shift_swap_request.benefactor.get_username_with_slack_verbal()} has taken the shift swap.",
"text": f" {shift_swap_request.benefactor.get_username_with_slack_verbal()} has accepted the shift swap request",
},
},
),
Expand All @@ -124,10 +136,9 @@ def _generate_blocks(self, shift_swap_request: "ShiftSwapRequest") -> Block.AnyB
"elements": [
{
"type": "button",
"style": "primary",
"text": {
"type": "plain_text",
"text": "✔️ Accept Shift Swap Request",
"text": "Accept",
"emoji": True,
},
"value": json.dumps(value),
Expand All @@ -138,24 +149,6 @@ def _generate_blocks(self, shift_swap_request: "ShiftSwapRequest") -> Block.AnyB
)
)

blocks.extend(
[
DIVIDER,
typing.cast(
Block.Context,
{
"type": "context",
"elements": [
{
"type": "mrkdwn",
"text": f"👀 View the shift swap within Grafana OnCall by clicking <{shift_swap_request.web_link}|here>.",
},
],
},
),
]
)

return blocks

def create_message(self, shift_swap_request: "ShiftSwapRequest") -> SlackMessage:
Expand Down Expand Up @@ -226,8 +219,9 @@ def _generate_blocks(shift_swap_request: "ShiftSwapRequest") -> Block.AnyBlocks:
"text": {
"type": "mrkdwn",
"text": (
f":exclamation: This shift swap request is still open and will start in {delta}.\n"
"Jump back into the thread and accept it if you're available!"
f"⚠️ This shift swap request for {_schedule_slack_url(shift_swap_request)} is "
f"still open and will start in {delta}. Jump back into the thread and accept it if "
"you're available!"
),
},
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,26 +40,16 @@ def test_generate_blocks(self, setup) -> None:
step = scenarios.BaseShiftSwapRequestStep(ssr.organization.slack_team_identity, ssr.organization)
blocks = step._generate_blocks(ssr)

assert (
blocks[0]["text"]["text"]
== f"Your teammate {beneficiary.get_username_with_slack_verbal()} has submitted a shift swap request."
assert blocks[0]["text"]["text"] == (
f"*New shift swap request for <{ssr.schedule.web_detail_page_link}|{ssr.schedule.name}>*\n"
f"Your teammate {beneficiary.get_username_with_slack_verbal()} has submitted a shift swap request."
)

accept_button = blocks[2]
accept_button = blocks[1]

assert accept_button["elements"][0]["text"]["text"] == "✔️ Accept Shift Swap Request"
assert accept_button["elements"][0]["text"]["text"] == "Accept"
assert accept_button["type"] == "actions"

assert blocks[3]["type"] == "divider"

context_section = blocks[4]

assert context_section["type"] == "context"
assert (
context_section["elements"][0]["text"]
== f"👀 View the shift swap within Grafana OnCall by clicking <{ssr.web_link}|here>."
)

@patch("apps.schedules.models.ShiftSwapRequest.shifts")
@pytest.mark.parametrize(
"shifts,expected_text",
Expand Down Expand Up @@ -108,7 +98,7 @@ def test_generate_blocks_shift_details(self, mock_shifts, setup, shifts, expecte
step = scenarios.BaseShiftSwapRequestStep(ssr.organization.slack_team_identity, ssr.organization)
blocks = step._generate_blocks(ssr)

assert blocks[1]["text"]["text"] == f"*📅 Shift Details*:\n\n{expected_text}"
assert blocks[1]["text"]["text"] == f"*Shift details*\n{expected_text}"

@pytest.mark.django_db
def test_generate_blocks_ssr_has_description(self, setup) -> None:
Expand All @@ -118,7 +108,7 @@ def test_generate_blocks_ssr_has_description(self, setup) -> None:
step = scenarios.BaseShiftSwapRequestStep(ssr.organization.slack_team_identity, ssr.organization)
blocks = step._generate_blocks(ssr)

assert blocks[2]["text"]["text"] == f"*📝 Description*: {description}"
assert blocks[1]["text"]["text"] == f"*Description*\n{description}"

@pytest.mark.django_db
def test_generate_blocks_ssr_is_deleted(self, setup) -> None:
Expand All @@ -128,7 +118,7 @@ def test_generate_blocks_ssr_is_deleted(self, setup) -> None:
step = scenarios.BaseShiftSwapRequestStep(ssr.organization.slack_team_identity, ssr.organization)
blocks = step._generate_blocks(ssr)

assert blocks[2]["text"]["text"] == "*Update*: this shift swap request has been deleted."
assert blocks[1]["text"]["text"] == " this shift swap request has been deleted"

@pytest.mark.django_db
def test_generate_blocks_ssr_is_taken(self, setup) -> None:
Expand All @@ -140,8 +130,8 @@ def test_generate_blocks_ssr_is_taken(self, setup) -> None:
blocks = step._generate_blocks(ssr)

assert (
blocks[2]["text"]["text"]
== f"*Update*: {benefactor.get_username_with_slack_verbal()} has taken the shift swap."
blocks[1]["text"]["text"]
== f" {benefactor.get_username_with_slack_verbal()} has accepted the shift swap request"
)

@patch("apps.slack.scenarios.shift_swap_requests.BaseShiftSwapRequestStep._generate_blocks")
Expand Down