fix: slack integration workflow (#2675)

* fix: slack integration workflow

* dev: add slack client id as configuration

* fix: clean up

* fix: added env to turbo

---------

Co-authored-by: sriram veeraghanta <veeraghanta.sriram@gmail.com>
This commit is contained in:
Nikhil 2023-11-06 21:00:49 +05:30 committed by GitHub
parent 984b36f45a
commit b372ccfdb3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 99 additions and 99 deletions

View File

@ -1,7 +1,7 @@
from .analytic import urlpatterns as analytic_urls from .analytic import urlpatterns as analytic_urls
from .asset import urlpatterns as asset_urls from .asset import urlpatterns as asset_urls
from .authentication import urlpatterns as authentication_urls from .authentication import urlpatterns as authentication_urls
from .configuration import urlpatterns as configuration_urls from .config import urlpatterns as configuration_urls
from .cycle import urlpatterns as cycle_urls from .cycle import urlpatterns as cycle_urls
from .estimate import urlpatterns as estimate_urls from .estimate import urlpatterns as estimate_urls
from .gpt import urlpatterns as gpt_urls from .gpt import urlpatterns as gpt_urls

View File

@ -30,4 +30,5 @@ class ConfigurationEndpoint(BaseAPIView):
data["email_password_login"] = ( data["email_password_login"] = (
os.environ.get("ENABLE_EMAIL_PASSWORD", "0") == "1" os.environ.get("ENABLE_EMAIL_PASSWORD", "0") == "1"
) )
data["slack"] = os.environ.get("SLACK_CLIENT_ID", None)
return Response(data, status=status.HTTP_200_OK) return Response(data, status=status.HTTP_200_OK)

View File

@ -1,6 +1,6 @@
# Python improts # Python improts
import uuid import uuid
import requests
# Django imports # Django imports
from django.contrib.auth.hashers import make_password from django.contrib.auth.hashers import make_password
@ -25,7 +25,7 @@ from plane.utils.integrations.github import (
delete_github_installation, delete_github_installation,
) )
from plane.api.permissions import WorkSpaceAdminPermission from plane.api.permissions import WorkSpaceAdminPermission
from plane.utils.integrations.slack import slack_oauth
class IntegrationViewSet(BaseViewSet): class IntegrationViewSet(BaseViewSet):
serializer_class = IntegrationSerializer serializer_class = IntegrationSerializer
@ -98,12 +98,19 @@ class WorkspaceIntegrationViewSet(BaseViewSet):
config = {"installation_id": installation_id} config = {"installation_id": installation_id}
if provider == "slack": if provider == "slack":
metadata = request.data.get("metadata", {}) code = request.data.get("code", False)
if not code:
return Response({"error": "Code is required"}, status=status.HTTP_400_BAD_REQUEST)
slack_response = slack_oauth(code=code)
metadata = slack_response
access_token = metadata.get("access_token", False) access_token = metadata.get("access_token", False)
team_id = metadata.get("team", {}).get("id", False) team_id = metadata.get("team", {}).get("id", False)
if not metadata or not access_token or not team_id: if not metadata or not access_token or not team_id:
return Response( return Response(
{"error": "Access token and team id is required"}, {"error": "Slack could not be installed. Please try again later"},
status=status.HTTP_400_BAD_REQUEST, status=status.HTTP_400_BAD_REQUEST,
) )
config = {"team_id": team_id, "access_token": access_token} config = {"team_id": team_id, "access_token": access_token}

View File

@ -11,6 +11,7 @@ from plane.api.views import BaseViewSet, BaseAPIView
from plane.db.models import SlackProjectSync, WorkspaceIntegration, ProjectMember from plane.db.models import SlackProjectSync, WorkspaceIntegration, ProjectMember
from plane.api.serializers import SlackProjectSyncSerializer from plane.api.serializers import SlackProjectSyncSerializer
from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission
from plane.utils.integrations.slack import slack_oauth
class SlackProjectSyncViewSet(BaseViewSet): class SlackProjectSyncViewSet(BaseViewSet):
@ -32,25 +33,46 @@ class SlackProjectSyncViewSet(BaseViewSet):
) )
def create(self, request, slug, project_id, workspace_integration_id): def create(self, request, slug, project_id, workspace_integration_id):
serializer = SlackProjectSyncSerializer(data=request.data) try:
code = request.data.get("code", False)
if not code:
return Response(
{"error": "Code is required"}, status=status.HTTP_400_BAD_REQUEST
)
slack_response = slack_oauth(code=code)
workspace_integration = WorkspaceIntegration.objects.get( workspace_integration = WorkspaceIntegration.objects.get(
workspace__slug=slug, pk=workspace_integration_id workspace__slug=slug, pk=workspace_integration_id
) )
if serializer.is_valid():
serializer.save(
project_id=project_id,
workspace_integration_id=workspace_integration_id,
)
workspace_integration = WorkspaceIntegration.objects.get( workspace_integration = WorkspaceIntegration.objects.get(
pk=workspace_integration_id, workspace__slug=slug pk=workspace_integration_id, workspace__slug=slug
) )
slack_project_sync = SlackProjectSync.objects.create(
access_token=slack_response.get("access_token"),
scopes=slack_response.get("scope"),
bot_user_id=slack_response.get("bot_user_id"),
webhook_url=slack_response.get("incoming_webhook", {}).get("url"),
data=slack_response,
team_id=slack_response.get("team", {}).get("id"),
team_name=slack_response.get("team", {}).get("name"),
workspace_integration=workspace_integration,
)
_ = ProjectMember.objects.get_or_create( _ = ProjectMember.objects.get_or_create(
member=workspace_integration.actor, role=20, project_id=project_id member=workspace_integration.actor, role=20, project_id=project_id
) )
serializer = SlackProjectSyncSerializer(slack_project_sync)
return Response(serializer.data, status=status.HTTP_200_OK) return Response(serializer.data, status=status.HTTP_200_OK)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) except IntegrityError as e:
if "already exists" in str(e):
return Response(
{"error": "Slack is already installed for the project"},
status=status.HTTP_410_GONE,
)
capture_exception(e)
return Response(
{"error": "Slack could not be installed. Please try again later"},
status=status.HTTP_400_BAD_REQUEST,
)

View File

@ -0,0 +1,20 @@
import os
import requests
def slack_oauth(code):
SLACK_OAUTH_URL = os.environ.get("SLACK_OAUTH_URL", False)
SLACK_CLIENT_ID = os.environ.get("SLACK_CLIENT_ID", False)
SLACK_CLIENT_SECRET = os.environ.get("SLACK_CLIENT_SECRET", False)
# Oauth Slack
if SLACK_OAUTH_URL and SLACK_CLIENT_ID and SLACK_CLIENT_SECRET:
response = requests.get(
SLACK_OAUTH_URL,
params={
"code": code,
"client_id": SLACK_CLIENT_ID,
"client_secret": SLACK_CLIENT_SECRET,
},
)
return response.json()
return {}

View File

@ -22,7 +22,8 @@
"SLACK_CLIENT_SECRET", "SLACK_CLIENT_SECRET",
"JITSU_TRACKER_ACCESS_KEY", "JITSU_TRACKER_ACCESS_KEY",
"JITSU_TRACKER_HOST", "JITSU_TRACKER_HOST",
"UNSPLASH_ACCESS_KEY" "UNSPLASH_ACCESS_KEY",
"NEXT_PUBLIC_SLACK_CLIENT_ID"
], ],
"pipeline": { "pipeline": {
"build": { "build": {

View File

@ -1,23 +0,0 @@
import axios from "axios";
import { NextApiRequest, NextApiResponse } from "next";
export default async function handleSlackAuthorize(req: NextApiRequest, res: NextApiResponse) {
try {
const { code } = req.body;
if (!code || code === "") return res.status(400).json({ message: "Code is required" });
const response = await axios({
method: "post",
url: process.env.SLACK_OAUTH_URL || "",
params: {
client_id: process.env.SLACK_CLIENT_ID,
client_secret: process.env.SLACK_CLIENT_SECRET,
code,
},
});
res.status(200).json(response?.data);
} catch (error) {
res.status(200).json({ message: "Internal Server Error" });
}
}

View File

@ -12,7 +12,7 @@ const appInstallationService = new AppInstallationService();
const AppPostInstallation: NextPageWithLayout = () => { const AppPostInstallation: NextPageWithLayout = () => {
const router = useRouter(); const router = useRouter();
const { installation_id, setup_action, state, provider, code } = router.query; const { installation_id, state, provider, code } = router.query;
useEffect(() => { useEffect(() => {
if (provider === "github" && state && installation_id) { if (provider === "github" && state && installation_id) {
@ -27,21 +27,15 @@ const AppPostInstallation: NextPageWithLayout = () => {
console.log(err); console.log(err);
}); });
} else if (provider === "slack" && state && code) { } else if (provider === "slack" && state && code) {
appInstallationService
.getSlackAuthDetails(code.toString())
.then((res) => {
const [workspaceSlug, projectId, integrationId] = state.toString().split(","); const [workspaceSlug, projectId, integrationId] = state.toString().split(",");
if (!projectId) { if (!projectId) {
const payload = { const payload = {
metadata: { code,
...res,
},
}; };
appInstallationService appInstallationService
.addInstallationApp(state.toString(), provider, payload) .addInstallationApp(state.toString(), provider, payload)
.then((r) => { .then(() => {
window.opener = null; window.opener = null;
window.open("", "_self"); window.open("", "_self");
window.close(); window.close();
@ -51,17 +45,11 @@ const AppPostInstallation: NextPageWithLayout = () => {
}); });
} else { } else {
const payload = { const payload = {
access_token: res.access_token, code,
bot_user_id: res.bot_user_id,
webhook_url: res.incoming_webhook.url,
data: res,
team_id: res.team.id,
team_name: res.team.name,
scopes: res.scope,
}; };
appInstallationService appInstallationService
.addSlackChannel(workspaceSlug, projectId, integrationId, payload) .addSlackChannel(workspaceSlug, projectId, integrationId, payload)
.then((r) => { .then(() => {
window.opener = null; window.opener = null;
window.open("", "_self"); window.open("", "_self");
window.close(); window.close();
@ -70,10 +58,6 @@ const AppPostInstallation: NextPageWithLayout = () => {
throw err.response; throw err.response;
}); });
} }
})
.catch((err) => {
console.log(err);
});
} }
}, [state, installation_id, provider, code]); }, [state, installation_id, provider, code]);

View File

@ -60,16 +60,4 @@ export class AppInstallationService extends APIService {
throw error?.response; throw error?.response;
}); });
} }
async getSlackAuthDetails(code: string): Promise<any> {
const response = await this.request({
method: "post",
url: "/api/slack-redirect",
data: {
code,
},
});
return response.data;
}
} }