Skip to content

Commit

Permalink
Support grafana escalate (#4453)
Browse files Browse the repository at this point in the history
# What this PR does
This PR adds support for **/grafana escalate** command alongside with
**/escalate.**
  • Loading branch information
Konstantinov-Innokentii authored Jun 5, 2024
1 parent f40634a commit 805d442
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 3 deletions.
36 changes: 36 additions & 0 deletions engine/apps/slack/slash_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
from apps.slack.types.interaction_payloads import SlashCommandPayload


class SlashCommand:
"""
SlashCommand represents slack slash command.
Attributes:
command -- command itself
args -- list of args passed to command
Examples:
/grafana escalate
SlashCommand(command='grafana', args=['escalate'])
"""

def __init__(self, command, args):
# command itself
self.command = command
# list of args passed to command
self.args = args

@property
def subcommand(self):
"""
Return first arg as subcommand
"""
return self.args[0] if len(self.args) > 0 else None

@staticmethod
def parse(payload: SlashCommandPayload):
"""
Parse slack slash command payload and return SlashCommand object
"""
command = payload["command"].lstrip("/")
args = payload["text"].split()
return SlashCommand(command, args)
84 changes: 83 additions & 1 deletion engine/apps/slack/tests/test_interactive_api_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from rest_framework.test import APIClient

from apps.slack.scenarios.manage_responders import ManageRespondersUserChange
from apps.slack.scenarios.paging import OnPagingTeamChange
from apps.slack.scenarios.paging import OnPagingTeamChange, StartDirectPaging
from apps.slack.scenarios.schedules import EditScheduleShiftNotifyStep
from apps.slack.scenarios.shift_swap_requests import AcceptShiftSwapRequestStep
from apps.slack.types import PayloadType
Expand Down Expand Up @@ -272,3 +272,85 @@ def test_accept_shift_swap_request(

assert response.status_code == status.HTTP_200_OK
mock_process_scenario.assert_called_once_with(slack_user_identity, slack_team_identity, payload)


@patch("apps.slack.views.SlackEventApiEndpointView.verify_signature", return_value=True)
@patch.object(StartDirectPaging, "process_scenario")
@pytest.mark.django_db
def test_grafana_escalate(
mock_process_scenario,
_mock_verify_signature,
make_organization,
make_slack_user_identity,
make_user,
slack_team_identity,
):
"""
Check StartDirectPaging.process_scenario gets called when a user types /grafana escalate.
UnifiedSlackApp commands are prefixed with /grafana.
"""
organization = make_organization(slack_team_identity=slack_team_identity)
slack_user_identity = make_slack_user_identity(slack_team_identity=slack_team_identity, slack_id=SLACK_USER_ID)
make_user(organization=organization, slack_user_identity=slack_user_identity)

payload = {
"token": "gIkuvaNzQIHg97ATvDxqgjtO",
"team_id": slack_team_identity.slack_id,
"team_domain": "example",
"enterprise_id": "E0001",
"enterprise_name": "Globular%20Construct%20Inc",
"channel_id": "C2147483705",
"channel_name": "test",
"user_id": slack_user_identity.slack_id,
"user_name": "Steve",
"command": "/grafana",
"text": "escalate",
"response_url": "https://hooks.slack.com/commands/1234/5678",
"trigger_id": "13345224609.738474920.8088930838d88f008e0",
"api": "api_value",
}
response = _make_request(payload)

assert response.status_code == status.HTTP_200_OK
mock_process_scenario.assert_called_once_with(slack_user_identity, slack_team_identity, payload)


@patch("apps.slack.views.SlackEventApiEndpointView.verify_signature", return_value=True)
@patch.object(StartDirectPaging, "process_scenario")
@pytest.mark.django_db
def test_escalate(
mock_process_scenario,
_mock_verify_signature,
make_organization,
make_slack_user_identity,
make_user,
slack_team_identity,
):
"""
Check StartDirectPaging.process_scenario gets called when a user types /escalate.
/escalate was used before Unified Slack App
"""
organization = make_organization(slack_team_identity=slack_team_identity)
slack_user_identity = make_slack_user_identity(slack_team_identity=slack_team_identity, slack_id=SLACK_USER_ID)
make_user(organization=organization, slack_user_identity=slack_user_identity)

payload = {
"token": "gIkuvaNzQIHg97ATvDxqgjtO",
"team_id": slack_team_identity.slack_id,
"team_domain": "example",
"enterprise_id": "E0001",
"enterprise_name": "Globular%20Construct%20Inc",
"channel_id": "C2147483705",
"channel_name": "test",
"user_id": slack_user_identity.slack_id,
"user_name": "Steve",
"command": "/escalate",
"text": "",
"response_url": "https://hooks.slack.com/commands/1234/5678",
"trigger_id": "13345224609.738474920.8088930838d88f008e0",
"api": "api_value",
}
response = _make_request(payload)

assert response.status_code == status.HTTP_200_OK
mock_process_scenario.assert_called_once_with(slack_user_identity, slack_team_identity, payload)
31 changes: 31 additions & 0 deletions engine/apps/slack/tests/test_slash_command.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from apps.slack.slash_command import SlashCommand


def test_parse():
payload = {
"command": "/grafana",
"text": "escalate",
"trigger_id": "trigger_id",
"user_id": "user_id",
"user_name": "user_name",
"api_app_id": "api_app_id",
}
slash_command = SlashCommand.parse(payload)
assert slash_command.command == "grafana"
assert slash_command.args == ["escalate"]
assert slash_command.subcommand == "escalate"


def test_parse_command_without_subcommand():
payload = {
"command": "/escalate",
"text": "",
"trigger_id": "trigger_id",
"user_id": "user_id",
"user_name": "user_name",
"api_app_id": "api_app_id",
}
slash_command = SlashCommand.parse(payload)
assert slash_command.command == "escalate"
assert slash_command.args == []
assert slash_command.subcommand is None
6 changes: 5 additions & 1 deletion engine/apps/slack/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
from .errors import SlackAPITokenError
from .installation import SlackInstallationExc, uninstall_slack_integration
from .models import SlackMessage, SlackTeamIdentity, SlackUserIdentity
from .slash_command import SlashCommand

SCENARIOS_ROUTES: ScenarioRoute.RoutingSteps = []
SCENARIOS_ROUTES.extend(ONBOARDING_STEPS_ROUTING)
Expand Down Expand Up @@ -354,7 +355,10 @@ def post(self, request):

# Slash commands have to "type"
if payload_command and route_payload_type == PayloadType.SLASH_COMMAND:
if payload_command in route["command_name"]:
cmd = SlashCommand.parse(payload)
# Check both command and subcommand for backward compatibility
# So both /grafana escalate and /escalate will work.
if cmd.command in route["command_name"] or cmd.subcommand in route["command_name"]:
Step = route["step"]
logger.info("Routing to {}".format(Step))
step = Step(slack_team_identity, organization, user)
Expand Down
2 changes: 1 addition & 1 deletion engine/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -678,7 +678,7 @@ class BrokerTypes:

SLACK_CLIENT_OAUTH_ID = os.environ.get("SLACK_CLIENT_OAUTH_ID")
SLACK_CLIENT_OAUTH_SECRET = os.environ.get("SLACK_CLIENT_OAUTH_SECRET")
SLACK_DIRECT_PAGING_SLASH_COMMAND = os.environ.get("SLACK_DIRECT_PAGING_SLASH_COMMAND", "/escalate")
SLACK_DIRECT_PAGING_SLASH_COMMAND = os.environ.get("SLACK_DIRECT_PAGING_SLASH_COMMAND", "/escalate").lstrip("/")

# Controls if slack integration can be installed/uninstalled.
SLACK_INTEGRATION_MAINTENANCE_ENABLED = os.environ.get("SLACK_INTEGRATION_MAINTENANCE_ENABLED", False)
Expand Down

0 comments on commit 805d442

Please sign in to comment.