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

Add Interactive Buttons for Slack message to approve and disapprove message from Slack #4393

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 13 additions & 6 deletions apps/base/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -251,12 +251,19 @@ def send_slack_notification(webhook=settings.SLACK_WEB_HOOK_URL, message=""):
message {str} -- JSON/Text message to be sent to slack (default: {""})
"""
try:
data = {
"attachments": [{"color": "ffaf4b", "fields": message["fields"]}],
"icon_url": "https://eval.ai/dist/images/evalai-logo-single.png",
"text": message["text"],
"username": "EvalAI",
}
# check if the message is a string or a dictionary
if isinstance(message, str):
data = {
"attachments": [
{"color": "ffaf4b", "fields": message["fields"]}
],
"icon_url": "https://eval.ai/dist/images/evalai-logo-single.png",
"text": message["text"],
"username": "EvalAI",
}
else:
data = message
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add condition to support 2 types of Slack message:

  • Normal text message
  • Message with interactive buttons (like the approve, disapprove)


return requests.post(
webhook,
data=json.dumps(data),
Expand Down
33 changes: 33 additions & 0 deletions apps/challenges/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,39 @@ def is_active(self):
return False


def slack_challenge_approval_callback(challenge_id):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A function similar to create_eks_cluster_or_ec2_for_challenge but used for Slack (cause we don't have instance and created if we call this function in our backend)

field_name = "approved_by_admin"
from challenges.models import Challenge
import challenges.aws_utils as aws

instance = Challenge.objects.get(id=challenge_id)

if challenge_id:
challenge = Challenge.objects.filter(id=challenge_id).first()
if challenge:
created = False
else:
created = True

if not created and is_model_field_changed(instance, field_name):
if (
instance.approved_by_admin is True
and instance.is_docker_based is True
and instance.remote_evaluation is False
):
serialized_obj = serializers.serialize("json", [instance])
aws.setup_eks_cluster.delay(serialized_obj)
elif (
instance.approved_by_admin is True
and instance.uses_ec2_worker is True
):
serialized_obj = serializers.serialize("json", [instance])
aws.setup_ec2.delay(serialized_obj)
aws.challenge_approval_callback(
instance=instance, field_name=field_name, sender="challenges.Challenge"
)


@receiver(signals.post_save, sender="challenges.Challenge")
def create_eks_cluster_or_ec2_for_challenge(sender, instance, created, **kwargs):
field_name = "approved_by_admin"
Expand Down
5 changes: 5 additions & 0 deletions apps/challenges/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,11 @@
views.get_or_update_challenge_phase_split,
name="get_or_update_challenge_phase_split",
),
url(
r"^challenge/slack_actions/$",
views.slack_actions,
name="challenge_slack_actions",
),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An open endpoint for slack to send any action to (any click or any interaction will be sent here)

url(
r"^(?P<challenge_pk>[0-9]+)/$",
views.star_challenge,
Expand Down
90 changes: 79 additions & 11 deletions apps/challenges/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
from os.path import basename, isfile, join
from datetime import datetime


from django.http import JsonResponse
from django.conf import settings
from django.contrib.auth.hashers import make_password
from django.contrib.auth.models import User
Expand All @@ -25,6 +25,7 @@
from django.http import HttpResponse
from django.utils import timezone

from rest_framework.permissions import AllowAny
from rest_framework import permissions, status
from rest_framework.decorators import (
api_view,
Expand Down Expand Up @@ -4638,6 +4639,49 @@ def update_allowed_email_ids(request, challenge_pk, phase_pk):
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)


@api_view(["POST"])
@authentication_classes([])
@permission_classes([AllowAny])
def slack_actions(request):
from . import models

payload = json.loads(request.POST.get("payload", "{}"))

action = payload["actions"][0]
action_type, challenge_id_str = action["value"].split("_")

challenge_id = int(challenge_id_str)

challenge = get_challenge_model(challenge_id)

# Verify the token
slack_token = payload.get('token')
expected_token = os.getenv('SLACK_VERIFICATION_TOKEN')

if slack_token != expected_token:
return JsonResponse({"error": "Invalid token"}, status=403)

if action_type == "approve":
challenge.approved_by_admin = True
challenge.save()

models.slack_challenge_approval_callback(challenge_id)

return JsonResponse(
{"text": f"Challenge {challenge_id} has been approved"}
)

else:
challenge.approved_by_admin = False
challenge.save()

models.slack_challenge_approval_callback(challenge_id)

return JsonResponse(
{"text": f"Challenge {challenge_id} has been disapproved"}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Handle the request from Slack, include our custom autherization method

)


@api_view(["GET"])
@throttle_classes([UserRateThrottle])
@permission_classes((permissions.IsAuthenticated, HasVerifiedEmail))
Expand Down Expand Up @@ -4679,17 +4723,41 @@ def request_challenge_approval_by_pk(request, challenge_pk):

message = {
"text": f"Challenge {challenge_pk} has finished submissions and has requested for approval!",
"fields": [
{
"title": "Admin URL",
"value": f"{evalai_api_server}/api/admin/challenges/challenge/{challenge_pk}",
"short": False,
},
"attachments": [
{
"title": "Challenge title",
"value": challenge.title,
"short": False,
},
"fallback": "You are unable to make a decision.",
"callback_id": "challenge_approval", # Callback ID used to identify this particular interaction
"color": "#3AA3E3",
"attachment_type": "default",
"fields": [
{
"title": "Admin URL",
"value": f"{evalai_api_server}/api/admin/challenges/challenge/{challenge_pk}",
"short": False,
},
{
"title": "Challenge title",
"value": challenge.title,
"short": False,
},
],
"actions": [
{
"name": "approval",
"text": "Yes",
"type": "button",
"value": f"approve_{challenge_pk}",
"style": "primary",
},
{
"name": "approval",
"text": "No",
"type": "button",
"value": f"disapprove_{challenge_pk}",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add buttons to the Slack Message

"style": "danger",
},
],
}
],
}

Expand Down