mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
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:
parent
984b36f45a
commit
b372ccfdb3
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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}
|
||||||
|
@ -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,
|
||||||
|
)
|
||||||
|
20
apiserver/plane/utils/integrations/slack.py
Normal file
20
apiserver/plane/utils/integrations/slack.py
Normal 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 {}
|
@ -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": {
|
||||||
|
@ -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" });
|
|
||||||
}
|
|
||||||
}
|
|
@ -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]);
|
||||||
|
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user