* Entity events insight logs * Insight logging * Fix event for updating templates * Format fixes * Remove organization_log_type.py * Simplify signature of chatops_insight_log * insight logs formatting * Add possibility to enable all insight logging via DynamicSetting * Fixes * Style fixes * Add migration * Fix migration
550 lines
26 KiB
Python
550 lines
26 KiB
Python
import hashlib
|
|
import hmac
|
|
import json
|
|
import logging
|
|
from typing import Optional
|
|
|
|
from django.conf import settings
|
|
from django.http import HttpResponse
|
|
from rest_framework import status
|
|
from rest_framework.permissions import IsAuthenticated
|
|
from rest_framework.response import Response
|
|
from rest_framework.views import APIView
|
|
|
|
from apps.api.permissions import IsAdmin, MethodPermission
|
|
from apps.auth_token.auth import PluginAuthentication
|
|
from apps.base.utils import live_settings
|
|
from apps.slack.scenarios.alertgroup_appearance import STEPS_ROUTING as ALERTGROUP_APPEARANCE_ROUTING
|
|
from apps.slack.scenarios.distribute_alerts import STEPS_ROUTING as DISTRIBUTION_STEPS_ROUTING
|
|
from apps.slack.scenarios.invited_to_channel import STEPS_ROUTING as INVITED_TO_CHANNEL_ROUTING
|
|
from apps.slack.scenarios.manual_incident import STEPS_ROUTING as MANUAL_INCIDENT_ROUTING
|
|
|
|
# Importing routes from scenarios
|
|
from apps.slack.scenarios.onboarding import STEPS_ROUTING as ONBOARDING_STEPS_ROUTING
|
|
from apps.slack.scenarios.profile_update import STEPS_ROUTING as PROFILE_UPDATE_ROUTING
|
|
from apps.slack.scenarios.resolution_note import STEPS_ROUTING as RESOLUTION_NOTE_ROUTING
|
|
from apps.slack.scenarios.scenario_step import (
|
|
EVENT_SUBTYPE_BOT_MESSAGE,
|
|
EVENT_SUBTYPE_FILE_SHARE,
|
|
EVENT_SUBTYPE_MESSAGE_CHANGED,
|
|
EVENT_SUBTYPE_MESSAGE_DELETED,
|
|
EVENT_TYPE_APP_MENTION,
|
|
EVENT_TYPE_MESSAGE,
|
|
EVENT_TYPE_MESSAGE_CHANNEL,
|
|
EVENT_TYPE_SUBTEAM_CREATED,
|
|
EVENT_TYPE_SUBTEAM_MEMBERS_CHANGED,
|
|
EVENT_TYPE_SUBTEAM_UPDATED,
|
|
EVENT_TYPE_USER_CHANGE,
|
|
PAYLOAD_TYPE_BLOCK_ACTIONS,
|
|
PAYLOAD_TYPE_DIALOG_SUBMISSION,
|
|
PAYLOAD_TYPE_EVENT_CALLBACK,
|
|
PAYLOAD_TYPE_INTERACTIVE_MESSAGE,
|
|
PAYLOAD_TYPE_MESSAGE_ACTION,
|
|
PAYLOAD_TYPE_SLASH_COMMAND,
|
|
PAYLOAD_TYPE_VIEW_SUBMISSION,
|
|
ScenarioStep,
|
|
)
|
|
from apps.slack.scenarios.schedules import STEPS_ROUTING as SCHEDULES_ROUTING
|
|
from apps.slack.scenarios.slack_channel import STEPS_ROUTING as CHANNEL_ROUTING
|
|
from apps.slack.scenarios.slack_channel_integration import STEPS_ROUTING as SLACK_CHANNEL_INTEGRATION_ROUTING
|
|
from apps.slack.scenarios.slack_usergroup import STEPS_ROUTING as SLACK_USERGROUP_UPDATE_ROUTING
|
|
from apps.slack.slack_client import SlackClientWithErrorHandling
|
|
from apps.slack.slack_client.exceptions import SlackAPIException, SlackAPITokenException
|
|
from apps.slack.tasks import clean_slack_integration_leftovers, unpopulate_slack_user_identities
|
|
from common.insight_log import ChatOpsEvent, ChatOpsType, write_chatops_insight_log
|
|
|
|
from .models import SlackActionRecord, SlackMessage, SlackTeamIdentity, SlackUserIdentity
|
|
|
|
SCENARIOS_ROUTES = [] # Add all other routes here
|
|
SCENARIOS_ROUTES.extend(ONBOARDING_STEPS_ROUTING)
|
|
SCENARIOS_ROUTES.extend(DISTRIBUTION_STEPS_ROUTING)
|
|
SCENARIOS_ROUTES.extend(INVITED_TO_CHANNEL_ROUTING)
|
|
SCENARIOS_ROUTES.extend(SCHEDULES_ROUTING)
|
|
SCENARIOS_ROUTES.extend(SLACK_CHANNEL_INTEGRATION_ROUTING)
|
|
SCENARIOS_ROUTES.extend(ALERTGROUP_APPEARANCE_ROUTING)
|
|
SCENARIOS_ROUTES.extend(RESOLUTION_NOTE_ROUTING)
|
|
SCENARIOS_ROUTES.extend(SLACK_USERGROUP_UPDATE_ROUTING)
|
|
SCENARIOS_ROUTES.extend(CHANNEL_ROUTING)
|
|
SCENARIOS_ROUTES.extend(PROFILE_UPDATE_ROUTING)
|
|
SCENARIOS_ROUTES.extend(MANUAL_INCIDENT_ROUTING)
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class StopAnalyticsReporting(APIView):
|
|
def get(self, request):
|
|
response = HttpResponse(
|
|
"Your app installation would not be tracked by analytics from backend, "
|
|
"use browser plugin to disable from a frontend side. "
|
|
)
|
|
response.set_cookie("no_track", True, max_age=10 * 360 * 24 * 60 * 60)
|
|
return response
|
|
|
|
|
|
class InstallLinkRedirectView(APIView):
|
|
def get(self, request, subscription="free", utm="not_specified"):
|
|
return HttpResponse(("Sign up is not allowed"), status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
class SignupRedirectView(APIView):
|
|
def get(self, request, subscription="free", utm="not_specified"):
|
|
return HttpResponse(("Sign up is not allowed"), status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
class OAuthSlackView(APIView):
|
|
def get(self, request, format=None, subscription="free", utm="not_specified"):
|
|
return HttpResponse(("Sign up is not allowed"), status=status.HTTP_400_BAD_REQUEST)
|
|
|
|
|
|
class SlackEventApiEndpointView(APIView):
|
|
@staticmethod
|
|
def verify_signature(timestamp, signature, body, secret):
|
|
# https://github.com/slackapi/python-slack-events-api/blob/master/slackeventsapi/server.py#L47
|
|
|
|
if hasattr(hmac, "compare_digest"):
|
|
req = str.encode("v0:" + str(timestamp) + ":") + body
|
|
request_hash = "v0=" + hmac.new(str.encode(secret), req, hashlib.sha256).hexdigest()
|
|
return hmac.compare_digest(request_hash, signature)
|
|
|
|
def get(self, request, format=None):
|
|
return Response("hello")
|
|
|
|
def post(self, request):
|
|
logger.info("Request id: {}".format(request.META.get("HTTP_X_REQUEST_ID")))
|
|
body = request.body
|
|
|
|
try:
|
|
slack_signature = request.META["HTTP_X_SLACK_SIGNATURE"]
|
|
slack_request_timestamp = request.META["HTTP_X_SLACK_REQUEST_TIMESTAMP"]
|
|
except KeyError:
|
|
logger.warning("X-Slack-Signature or X-Slack-Request_Timestamp don't exist, This request is not from slack")
|
|
return Response(status=403)
|
|
|
|
if not settings.DEBUG:
|
|
if live_settings.SLACK_SIGNING_SECRET is None and settings.SLACK_SIGNING_SECRET_LIVE:
|
|
raise Exception("Please specify SLACK_SIGNING_SECRET or use DEBUG.")
|
|
|
|
if not (
|
|
SlackEventApiEndpointView.verify_signature(
|
|
slack_request_timestamp, slack_signature, body, live_settings.SLACK_SIGNING_SECRET
|
|
)
|
|
or SlackEventApiEndpointView.verify_signature(
|
|
slack_request_timestamp, slack_signature, body, settings.SLACK_SIGNING_SECRET_LIVE
|
|
)
|
|
):
|
|
return Response(status=403)
|
|
|
|
# Unifying payload
|
|
if "payload" in request.data:
|
|
payload = request.data["payload"]
|
|
else:
|
|
payload = request.data
|
|
if isinstance(payload, str):
|
|
payload = json.JSONDecoder().decode(payload)
|
|
|
|
# Checking if it's repeated Slack request
|
|
if "HTTP_X_SLACK_RETRY_NUM" in request.META and int(request.META["HTTP_X_SLACK_RETRY_NUM"]) > 1:
|
|
logger.critical(
|
|
"Slack retries {} time, request data: {}".format(request.META["HTTP_X_SLACK_RETRY_NUM"], request.data)
|
|
)
|
|
payload["amixr_slack_retries"] = request.META["HTTP_X_SLACK_RETRY_NUM"]
|
|
|
|
# Initial url verification
|
|
if "type" in payload and payload["type"] == "url_verification":
|
|
logger.critical("URL verification from Slack side. That's suspicious.")
|
|
return Response(payload["challenge"])
|
|
|
|
# Linking team
|
|
slack_team_identity = self._get_slack_team_identity_from_payload(payload)
|
|
|
|
if not slack_team_identity:
|
|
logger.info("Dropping request because it does not have SlackTeamIdentity.")
|
|
return Response()
|
|
|
|
# Means that slack_team_identity unpopulated
|
|
if not slack_team_identity.organizations.exists():
|
|
logger.warning(f"OnCall Team for SlackTeamIdentity is not detected, stop it!")
|
|
# Open pop-up to inform user why OnCall bot doesn't work if any action was triggered
|
|
warning_text = (
|
|
"OnCall is not able to process this action because this Slack workspace was "
|
|
"disconnected from OnCall. Please log in to the OnCall web interface and install "
|
|
"Slack Integration with this workspace again."
|
|
)
|
|
self._open_warning_window_if_needed(payload, slack_team_identity, warning_text)
|
|
return Response(status=200)
|
|
|
|
# Todo: the case when team has no keys is unexpected, investigation is required
|
|
if slack_team_identity.access_token is None and slack_team_identity.bot_access_token is None:
|
|
logger.info(f"Team {slack_team_identity.slack_id} has no keys, dropping request.")
|
|
return Response()
|
|
|
|
sc = SlackClientWithErrorHandling(slack_team_identity.bot_access_token)
|
|
|
|
if slack_team_identity.detected_token_revoked is not None:
|
|
# check if token is still invalid
|
|
try:
|
|
sc.api_call(
|
|
"auth.test",
|
|
team=slack_team_identity,
|
|
)
|
|
except SlackAPITokenException:
|
|
logger.info(f"Team {slack_team_identity.slack_id} has revoked token, dropping request.")
|
|
return Response(status=200)
|
|
|
|
Step = None
|
|
step_was_found = False
|
|
|
|
slack_user_id = None
|
|
user = None
|
|
# Linking user identity
|
|
slack_user_identity = None
|
|
|
|
if "event" in payload and payload["event"] is not None:
|
|
if ("user" in payload["event"]) and slack_team_identity and (payload["event"]["user"] is not None):
|
|
if "id" in payload["event"]["user"]:
|
|
slack_user_id = payload["event"]["user"]["id"]
|
|
elif type(payload["event"]["user"]) is str:
|
|
slack_user_id = payload["event"]["user"]
|
|
else:
|
|
raise Exception("Failed Linking user identity")
|
|
|
|
elif (
|
|
("bot_id" in payload["event"])
|
|
and slack_team_identity
|
|
and (
|
|
payload["event"]["bot_id"] is not None
|
|
and "channel_type" in payload["event"]
|
|
and payload["event"]["channel_type"] == EVENT_TYPE_MESSAGE_CHANNEL
|
|
)
|
|
):
|
|
response = sc.api_call("bots.info", bot=payload["event"]["bot_id"])
|
|
bot_user_id = response.get("bot", {}).get("user_id", "")
|
|
|
|
# Don't react on own bot's messages.
|
|
if bot_user_id == slack_team_identity.bot_user_id:
|
|
return Response(status=200)
|
|
|
|
elif "user" in payload["event"].get("message", {}):
|
|
slack_user_id = payload["event"]["message"]["user"]
|
|
# event subtype 'message_deleted'
|
|
elif "user" in payload["event"].get("previous_message", {}):
|
|
slack_user_id = payload["event"]["previous_message"]["user"]
|
|
|
|
if "user" in payload:
|
|
slack_user_id = payload["user"]["id"]
|
|
|
|
elif "user_id" in payload:
|
|
slack_user_id = payload["user_id"]
|
|
|
|
if slack_user_id is not None and slack_user_id != slack_team_identity.bot_user_id:
|
|
slack_user_identity = SlackUserIdentity.objects.filter(
|
|
slack_id=slack_user_id,
|
|
slack_team_identity=slack_team_identity,
|
|
).first()
|
|
|
|
organization = self._get_organization_from_payload(payload, slack_team_identity)
|
|
logger.info("Organization: " + str(organization))
|
|
logger.info("SlackUserIdentity detected: " + str(slack_user_identity))
|
|
|
|
if not slack_user_identity:
|
|
if "type" in payload and payload["type"] == PAYLOAD_TYPE_EVENT_CALLBACK:
|
|
if payload["event"]["type"] in [
|
|
EVENT_TYPE_SUBTEAM_CREATED,
|
|
EVENT_TYPE_SUBTEAM_UPDATED,
|
|
EVENT_TYPE_SUBTEAM_MEMBERS_CHANGED,
|
|
]:
|
|
logger.info("Slack event without user slack_id.")
|
|
elif payload["event"]["type"] == EVENT_TYPE_USER_CHANGE:
|
|
logger.info("Event user_change. Dropping request because it does not have SlackUserIdentity.")
|
|
return Response()
|
|
else:
|
|
logger.info("Dropping request because it does not have SlackUserIdentity.")
|
|
self._open_warning_for_unconnected_user(sc, payload)
|
|
return Response()
|
|
elif organization:
|
|
user = slack_user_identity.get_user(organization)
|
|
if not user:
|
|
# Means that user slack_user_identity is not in any organization, connected to this Slack workspace
|
|
warning_text = "Permission denied. Please connect your Slack account to OnCall."
|
|
# Open pop-up to inform user why OnCall bot doesn't work if any action was triggered
|
|
self._open_warning_window_if_needed(payload, slack_team_identity, warning_text)
|
|
return Response(status=200)
|
|
|
|
action_record = SlackActionRecord(user=user, organization=organization, payload=payload)
|
|
|
|
# Capture cases when we expect stateful message from user
|
|
if not step_was_found and "type" in payload and payload["type"] == PAYLOAD_TYPE_EVENT_CALLBACK:
|
|
# Message event is from channel
|
|
if (
|
|
payload["event"]["type"] == EVENT_TYPE_MESSAGE
|
|
and payload["event"]["channel_type"] == EVENT_TYPE_MESSAGE_CHANNEL
|
|
and (
|
|
"subtype" not in payload["event"]
|
|
or payload["event"]["subtype"] == EVENT_SUBTYPE_BOT_MESSAGE
|
|
or payload["event"]["subtype"] == EVENT_SUBTYPE_MESSAGE_CHANGED
|
|
or payload["event"]["subtype"] == EVENT_SUBTYPE_FILE_SHARE
|
|
or payload["event"]["subtype"] == EVENT_SUBTYPE_MESSAGE_DELETED
|
|
)
|
|
):
|
|
for route in SCENARIOS_ROUTES:
|
|
if (
|
|
"message_channel_type" in route
|
|
and payload["event"]["channel_type"] == route["message_channel_type"]
|
|
):
|
|
Step = route["step"]
|
|
logger.info("Routing to {}".format(Step))
|
|
step = Step(slack_team_identity, organization, user)
|
|
step.dispatch(slack_user_identity, slack_team_identity, payload)
|
|
step_was_found = True
|
|
# We don't do anything on app mention, but we doesn't want to unsubscribe from this event yet.
|
|
if payload["event"]["type"] == EVENT_TYPE_APP_MENTION:
|
|
logger.info(f"Received event of type {EVENT_TYPE_APP_MENTION} from slack. Skipping.")
|
|
return Response(status=200)
|
|
# Routing to Steps based on routing rules
|
|
try:
|
|
if not step_was_found:
|
|
for route in SCENARIOS_ROUTES:
|
|
# Slash commands have to "type"
|
|
if "command" in payload and route["payload_type"] == PAYLOAD_TYPE_SLASH_COMMAND:
|
|
if payload["command"] in route["command_name"]:
|
|
Step = route["step"]
|
|
action_record.step = Step.routing_uid()
|
|
logger.info("Routing to {}".format(Step))
|
|
step = Step(slack_team_identity, organization, user)
|
|
step.dispatch(slack_user_identity, slack_team_identity, payload)
|
|
step_was_found = True
|
|
|
|
if "type" in payload and payload["type"] == route["payload_type"]:
|
|
if payload["type"] == PAYLOAD_TYPE_EVENT_CALLBACK:
|
|
if payload["event"]["type"] == route["event_type"]:
|
|
# event_name is used for stateful
|
|
if "event_name" not in route:
|
|
Step = route["step"]
|
|
action_record.step = Step.routing_uid()
|
|
logger.info("Routing to {}".format(Step))
|
|
step = Step(slack_team_identity, organization, user)
|
|
step.dispatch(slack_user_identity, slack_team_identity, payload)
|
|
step_was_found = True
|
|
|
|
if payload["type"] == PAYLOAD_TYPE_INTERACTIVE_MESSAGE:
|
|
for action in payload["actions"]:
|
|
if action["type"] == route["action_type"]:
|
|
# Action name may also contain action arguments.
|
|
# So only beginning is used for routing.
|
|
if action["name"].startswith(route["action_name"]):
|
|
Step = route["step"]
|
|
action_record.step = Step.routing_uid()
|
|
logger.info("Routing to {}".format(Step))
|
|
step = Step(slack_team_identity, organization, user)
|
|
result = step.dispatch(slack_user_identity, slack_team_identity, payload)
|
|
if result is not None:
|
|
return result
|
|
step_was_found = True
|
|
|
|
if payload["type"] == PAYLOAD_TYPE_BLOCK_ACTIONS:
|
|
for action in payload["actions"]:
|
|
if action["type"] == route["block_action_type"]:
|
|
if action["action_id"].startswith(route["block_action_id"]):
|
|
Step = route["step"]
|
|
action_record.step = Step.routing_uid()
|
|
logger.info("Routing to {}".format(Step))
|
|
step = Step(slack_team_identity, organization, user)
|
|
step.dispatch(slack_user_identity, slack_team_identity, payload)
|
|
step_was_found = True
|
|
|
|
if payload["type"] == PAYLOAD_TYPE_DIALOG_SUBMISSION:
|
|
if payload["callback_id"] == route["dialog_callback_id"]:
|
|
Step = route["step"]
|
|
action_record.step = Step.routing_uid()
|
|
logger.info("Routing to {}".format(Step))
|
|
step = Step(slack_team_identity, organization, user)
|
|
result = step.dispatch(slack_user_identity, slack_team_identity, payload)
|
|
if result is not None:
|
|
return result
|
|
step_was_found = True
|
|
|
|
if payload["type"] == PAYLOAD_TYPE_VIEW_SUBMISSION:
|
|
if payload["view"]["callback_id"].startswith(route["view_callback_id"]):
|
|
Step = route["step"]
|
|
action_record.step = Step.routing_uid()
|
|
logger.info("Routing to {}".format(Step))
|
|
step = Step(slack_team_identity, organization, user)
|
|
result = step.dispatch(slack_user_identity, slack_team_identity, payload)
|
|
if result is not None:
|
|
return result
|
|
step_was_found = True
|
|
|
|
if payload["type"] == PAYLOAD_TYPE_MESSAGE_ACTION:
|
|
if payload["callback_id"] in route["message_action_callback_id"]:
|
|
Step = route["step"]
|
|
action_record.step = Step.routing_uid()
|
|
logger.info("Routing to {}".format(Step))
|
|
step = Step(slack_team_identity, organization, user)
|
|
step.dispatch(slack_user_identity, slack_team_identity, payload)
|
|
step_was_found = True
|
|
|
|
finally:
|
|
if Step is not None and Step.need_to_be_logged and organization:
|
|
action_record.save()
|
|
|
|
if not step_was_found:
|
|
raise Exception("Step is undefined" + str(payload))
|
|
|
|
return Response(status=200)
|
|
|
|
def _get_slack_team_identity_from_payload(self, payload) -> Optional[SlackTeamIdentity]:
|
|
slack_team_identity = None
|
|
|
|
if "team" in payload:
|
|
slack_team_id = payload["team"]["id"]
|
|
elif "team_id" in payload:
|
|
slack_team_id = payload["team_id"]
|
|
else:
|
|
return slack_team_identity
|
|
|
|
try:
|
|
slack_team_identity = SlackTeamIdentity.objects.get(slack_id=slack_team_id)
|
|
except SlackTeamIdentity.DoesNotExist as e:
|
|
logger.warning("Team identity not detected, that's dangerous!" + str(e))
|
|
return slack_team_identity
|
|
|
|
def _get_organization_from_payload(self, payload, slack_team_identity):
|
|
message_ts = None
|
|
channel_id = None
|
|
organization = None
|
|
|
|
# view submission or actions in view
|
|
if "view" in payload:
|
|
organization_id = None
|
|
private_metadata = payload["view"].get("private_metadata")
|
|
# steps with private_metadata in which we know organization before open view
|
|
if private_metadata and "organization_id" in private_metadata:
|
|
organization_id = json.loads(private_metadata).get("organization_id")
|
|
# steps with organization selection in view (e.g. slash commands)
|
|
elif ScenarioStep.SELECT_ORGANIZATION_AND_ROUTE_BLOCK_ID in payload["view"].get("state", {}).get(
|
|
"values", {}
|
|
):
|
|
payload_values = payload["view"]["state"]["values"]
|
|
selected_value = payload_values[ScenarioStep.SELECT_ORGANIZATION_AND_ROUTE_BLOCK_ID][
|
|
ScenarioStep.SELECT_ORGANIZATION_AND_ROUTE_BLOCK_ID
|
|
]["selected_option"]["value"]
|
|
organization_id = int(selected_value.split("-")[0])
|
|
if organization_id:
|
|
organization = slack_team_identity.organizations.get(pk=organization_id)
|
|
return organization
|
|
# buttons and actions
|
|
elif payload.get("type") in [
|
|
PAYLOAD_TYPE_BLOCK_ACTIONS,
|
|
PAYLOAD_TYPE_INTERACTIVE_MESSAGE,
|
|
PAYLOAD_TYPE_MESSAGE_ACTION,
|
|
]:
|
|
# for cases when we put organization_id into action value (e.g. public suggestion)
|
|
if (
|
|
payload.get("actions")
|
|
and payload["actions"][0].get("value", {})
|
|
and "organization_id" in payload["actions"][0]["value"]
|
|
):
|
|
organization_id = int(json.loads(payload["actions"][0]["value"])["organization_id"])
|
|
organization = slack_team_identity.organizations.get(pk=organization_id)
|
|
return organization
|
|
|
|
channel_id = payload["channel"]["id"]
|
|
if "message" in payload:
|
|
message_ts = payload["message"].get("thread_ts") or payload["message"]["ts"]
|
|
# for interactive message
|
|
elif "message_ts" in payload:
|
|
message_ts = payload["message_ts"]
|
|
else:
|
|
return
|
|
# events
|
|
elif payload.get("type") == PAYLOAD_TYPE_EVENT_CALLBACK:
|
|
if "channel" in payload["event"]: # events without channel: user_change, events with subteam, etc.
|
|
channel_id = payload["event"]["channel"]
|
|
|
|
if "message" in payload["event"]:
|
|
message_ts = payload["event"]["message"].get("thread_ts") or payload["event"]["message"]["ts"]
|
|
elif "thread_ts" in payload["event"]:
|
|
message_ts = payload["event"]["thread_ts"]
|
|
else:
|
|
return
|
|
|
|
if not (message_ts and channel_id):
|
|
return
|
|
|
|
try:
|
|
slack_message = SlackMessage.objects.get(
|
|
slack_id=message_ts,
|
|
_slack_team_identity=slack_team_identity,
|
|
channel_id=channel_id,
|
|
)
|
|
except SlackMessage.DoesNotExist:
|
|
pass
|
|
else:
|
|
alert_group = slack_message.get_alert_group()
|
|
if alert_group:
|
|
organization = alert_group.channel.organization
|
|
return organization
|
|
return organization
|
|
|
|
def _open_warning_window_if_needed(self, payload, slack_team_identity, warning_text) -> None:
|
|
if payload.get("trigger_id") is not None:
|
|
step = ScenarioStep(slack_team_identity)
|
|
try:
|
|
step.open_warning_window(payload, warning_text)
|
|
except SlackAPIException as e:
|
|
logger.info(
|
|
f"Failed to open pop-up for unpopulated SlackTeamIdentity {slack_team_identity.pk}\n" f"Error: {e}"
|
|
)
|
|
|
|
def _open_warning_for_unconnected_user(self, slack_client, payload):
|
|
if payload.get("trigger_id") is None:
|
|
return
|
|
|
|
text = (
|
|
"Your Grafana account is not connected to your Slack account. :flushed:\n"
|
|
"That's very easy to fix. Please go to the *Grafana* -> *OnCall* -> *Users*, "
|
|
"choose *your profile* and click the *connect* button.\n"
|
|
":rocket: :rocket: :rocket:"
|
|
)
|
|
|
|
view = {
|
|
"blocks": (
|
|
{"type": "section", "block_id": "section-identifier", "text": {"type": "mrkdwn", "text": text}},
|
|
),
|
|
"type": "modal",
|
|
"callback_id": "modal-identifier",
|
|
"title": {
|
|
"type": "plain_text",
|
|
"text": "One more step!",
|
|
},
|
|
}
|
|
slack_client.api_call(
|
|
"views.open",
|
|
trigger_id=payload["trigger_id"],
|
|
view=view,
|
|
)
|
|
|
|
|
|
class ResetSlackView(APIView):
|
|
|
|
permission_classes = (IsAuthenticated, MethodPermission)
|
|
authentication_classes = [PluginAuthentication]
|
|
|
|
method_permissions = {IsAdmin: {"POST"}}
|
|
|
|
def post(self, request):
|
|
organization = request.auth.organization
|
|
slack_team_identity = organization.slack_team_identity
|
|
if slack_team_identity is not None:
|
|
clean_slack_integration_leftovers.apply_async((organization.pk,))
|
|
write_chatops_insight_log(
|
|
author=request.user,
|
|
event_name=ChatOpsEvent.WORKSPACE_DISCONNECTED,
|
|
chatops_type=ChatOpsType.SLACK,
|
|
)
|
|
unpopulate_slack_user_identities(organization.pk, True)
|
|
response = Response(status=200)
|
|
else:
|
|
response = Response(status=400)
|
|
|
|
return response
|