Introduce slash command matcher (#4717)
With the Unified Slack app we now have two ways of calling commands. 1. Legacy one when command invoked directly: /escalate 2. Unified one: /grafana escalate On top of that we have different slach commands for each environment: /escalate-local, /escalate-dev, etc. It was leading to a weird command to escalate via Unified App in dev u need to type: /grafana-dev escalate-develop. To support both, I introduced a matcher function for SlashCommandRoutes. It allows to simplify handling of such cases without complex workarounds in an EventAPIEndpoint. # What this PR does ## Which issue(s) this PR closes Related to [issue link here] <!-- *Note*: If you want the issue to be auto-closed once the PR is merged, change "Related to" to "Closes" in the line above. If you have more than one GitHub issue that this PR closes, be sure to preface each issue link with a [closing keyword](https://docs.github.com/en/get-started/writing-on-github/working-with-advanced-formatting/using-keywords-in-issues-and-pull-requests#linking-a-pull-request-to-an-issue). This ensures that the issue(s) are auto-closed once the PR has been merged. --> ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [ ] Documentation added (or `pr:no public docs` PR label added if not required) - [ ] Added the relevant release notes label (see labels prefixed w/ `release:`). These labels dictate how your PR will show up in the autogenerated release notes.
This commit is contained in:
parent
4877b9d927
commit
94219c25bb
5 changed files with 22 additions and 5 deletions
|
|
@ -15,6 +15,7 @@ from apps.slack.chatops_proxy_routing import make_private_metadata, make_value
|
|||
from apps.slack.constants import DIVIDER, PRIVATE_METADATA_MAX_LENGTH
|
||||
from apps.slack.errors import SlackAPIChannelNotFoundError
|
||||
from apps.slack.scenarios import scenario_step
|
||||
from apps.slack.slash_command import SlashCommand
|
||||
from apps.slack.types import (
|
||||
Block,
|
||||
BlockActionType,
|
||||
|
|
@ -115,7 +116,13 @@ def get_current_items(
|
|||
class StartDirectPaging(scenario_step.ScenarioStep):
|
||||
"""Handle slash command invocation and show initial dialog."""
|
||||
|
||||
command_name = [settings.SLACK_DIRECT_PAGING_SLASH_COMMAND]
|
||||
@staticmethod
|
||||
def matcher(slash_command: SlashCommand) -> bool:
|
||||
# Check if command is /escalate. It's a legacy command we keep for smooth transition.
|
||||
is_legacy_command = slash_command.command == settings.SLACK_DIRECT_PAGING_SLASH_COMMAND
|
||||
# Check if command is /grafana escalate. It's a new command from unified app.
|
||||
is_unified_app_command = slash_command.is_grafana_command and slash_command.subcommand == "escalate"
|
||||
return is_legacy_command or is_unified_app_command
|
||||
|
||||
def process_scenario(
|
||||
self,
|
||||
|
|
@ -995,8 +1002,8 @@ STEPS_ROUTING: ScenarioRoute.RoutingSteps = [
|
|||
},
|
||||
{
|
||||
"payload_type": PayloadType.SLASH_COMMAND,
|
||||
"command_name": StartDirectPaging.command_name,
|
||||
"step": StartDirectPaging,
|
||||
"matcher": StartDirectPaging.matcher,
|
||||
},
|
||||
{
|
||||
"payload_type": PayloadType.VIEW_SUBMISSION,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,8 @@ class SlashCommand:
|
|||
@property
|
||||
def subcommand(self):
|
||||
"""
|
||||
Return first arg as subcommand
|
||||
Return first arg as action subcommand: part of command which defines action
|
||||
Example: /grafana escalate -> escalate
|
||||
"""
|
||||
return self.args[0] if len(self.args) > 0 else None
|
||||
|
||||
|
|
@ -34,3 +35,7 @@ class SlashCommand:
|
|||
command = payload["command"].lstrip("/")
|
||||
args = payload["text"].split()
|
||||
return SlashCommand(command, args)
|
||||
|
||||
@property
|
||||
def is_grafana_command(self):
|
||||
return self.command in ["grafana", "grafana-dev", "grafana-ops", "grafana-prod"]
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ def test_parse():
|
|||
assert slash_command.command == "grafana"
|
||||
assert slash_command.args == ["escalate"]
|
||||
assert slash_command.subcommand == "escalate"
|
||||
assert slash_command.is_grafana_command
|
||||
|
||||
|
||||
def test_parse_command_without_subcommand():
|
||||
|
|
|
|||
|
|
@ -1,11 +1,15 @@
|
|||
import typing
|
||||
|
||||
from apps.slack.slash_command import SlashCommand
|
||||
|
||||
from .common import EventType, PayloadType
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from apps.slack.scenarios.scenario_step import ScenarioStep
|
||||
from apps.slack.types import BlockActionType, InteractiveMessageActionType
|
||||
|
||||
MatcherType = typing.Callable[[SlashCommand], bool]
|
||||
|
||||
|
||||
class ScenarioRoute:
|
||||
class _Base(typing.TypedDict):
|
||||
|
|
@ -32,7 +36,7 @@ class ScenarioRoute:
|
|||
|
||||
class SlashCommandScenarioRoute(_Base):
|
||||
payload_type: typing.Literal[PayloadType.SLASH_COMMAND]
|
||||
command_name: typing.List[str]
|
||||
matcher: MatcherType
|
||||
|
||||
class ViewSubmissionScenarioRoute(_Base):
|
||||
payload_type: typing.Literal[PayloadType.VIEW_SUBMISSION]
|
||||
|
|
|
|||
|
|
@ -361,7 +361,7 @@ class SlackEventApiEndpointView(APIView):
|
|||
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"]:
|
||||
if route["matcher"](cmd):
|
||||
Step = route["step"]
|
||||
logger.info("Routing to {}".format(Step))
|
||||
step = Step(slack_team_identity, organization, user)
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue