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 .asset import urlpatterns as asset_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 .estimate import urlpatterns as estimate_urls
from .gpt import urlpatterns as gpt_urls

View File

@ -30,4 +30,5 @@ class ConfigurationEndpoint(BaseAPIView):
data["email_password_login"] = (
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)

View File

@ -1,6 +1,6 @@
# Python improts
import uuid
import requests
# Django imports
from django.contrib.auth.hashers import make_password
@ -25,7 +25,7 @@ from plane.utils.integrations.github import (
delete_github_installation,
)
from plane.api.permissions import WorkSpaceAdminPermission
from plane.utils.integrations.slack import slack_oauth
class IntegrationViewSet(BaseViewSet):
serializer_class = IntegrationSerializer
@ -98,12 +98,19 @@ class WorkspaceIntegrationViewSet(BaseViewSet):
config = {"installation_id": installation_id}
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)
team_id = metadata.get("team", {}).get("id", False)
if not metadata or not access_token or not team_id:
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,
)
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.api.serializers import SlackProjectSyncSerializer
from plane.api.permissions import ProjectBasePermission, ProjectEntityPermission
from plane.utils.integrations.slack import slack_oauth
class SlackProjectSyncViewSet(BaseViewSet):
@ -32,25 +33,46 @@ class SlackProjectSyncViewSet(BaseViewSet):
)
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__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(
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(
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.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",
"JITSU_TRACKER_ACCESS_KEY",
"JITSU_TRACKER_HOST",
"UNSPLASH_ACCESS_KEY"
"UNSPLASH_ACCESS_KEY",
"NEXT_PUBLIC_SLACK_CLIENT_ID"
],
"pipeline": {
"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 router = useRouter();
const { installation_id, setup_action, state, provider, code } = router.query;
const { installation_id, state, provider, code } = router.query;
useEffect(() => {
if (provider === "github" && state && installation_id) {
@ -27,21 +27,15 @@ const AppPostInstallation: NextPageWithLayout = () => {
console.log(err);
});
} else if (provider === "slack" && state && code) {
appInstallationService
.getSlackAuthDetails(code.toString())
.then((res) => {
const [workspaceSlug, projectId, integrationId] = state.toString().split(",");
if (!projectId) {
const payload = {
metadata: {
...res,
},
code,
};
appInstallationService
.addInstallationApp(state.toString(), provider, payload)
.then((r) => {
.then(() => {
window.opener = null;
window.open("", "_self");
window.close();
@ -51,17 +45,11 @@ const AppPostInstallation: NextPageWithLayout = () => {
});
} else {
const payload = {
access_token: res.access_token,
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,
code,
};
appInstallationService
.addSlackChannel(workspaceSlug, projectId, integrationId, payload)
.then((r) => {
.then(() => {
window.opener = null;
window.open("", "_self");
window.close();
@ -70,10 +58,6 @@ const AppPostInstallation: NextPageWithLayout = () => {
throw err.response;
});
}
})
.catch((err) => {
console.log(err);
});
}
}, [state, installation_id, provider, code]);

View File

@ -60,16 +60,4 @@ export class AppInstallationService extends APIService {
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;
}
}