From 202642774298dfa74af707665b3ba2a4257fb1a1 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 6 Nov 2023 19:34:19 +0530 Subject: [PATCH 1/4] fix: slack integration workflow --- apiserver/plane/api/views/integration/base.py | 15 +++- .../plane/api/views/integration/slack.py | 44 ++++++++--- apiserver/plane/utils/integrations/slack.py | 20 +++++ web/pages/installations/[provider]/index.tsx | 75 ++++++++----------- 4 files changed, 94 insertions(+), 60 deletions(-) create mode 100644 apiserver/plane/utils/integrations/slack.py diff --git a/apiserver/plane/api/views/integration/base.py b/apiserver/plane/api/views/integration/base.py index 65b94d0a1e5..cc911b53716 100644 --- a/apiserver/plane/api/views/integration/base.py +++ b/apiserver/plane/api/views/integration/base.py @@ -1,6 +1,6 @@ # Python improts import uuid - +import requests # Django imports from django.contrib.auth.hashers import make_password @@ -25,7 +25,7 @@ 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 @@ def create(self, request, slug, provider): 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} diff --git a/apiserver/plane/api/views/integration/slack.py b/apiserver/plane/api/views/integration/slack.py index 83aa951baca..863b6ba0cf2 100644 --- a/apiserver/plane/api/views/integration/slack.py +++ b/apiserver/plane/api/views/integration/slack.py @@ -11,6 +11,7 @@ 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 @@ def get_queryset(self): ) def create(self, request, slug, project_id, workspace_integration_id): - serializer = SlackProjectSyncSerializer(data=request.data) + try: + code = request.data.get("code", False) - workspace_integration = WorkspaceIntegration.objects.get( - workspace__slug=slug, pk=workspace_integration_id - ) + if not code: + return Response( + {"error": "Code is required"}, status=status.HTTP_400_BAD_REQUEST + ) + + slack_response = slack_oauth(code=code) - if serializer.is_valid(): - serializer.save( - project_id=project_id, - workspace_integration_id=workspace_integration_id, + workspace_integration = WorkspaceIntegration.objects.get( + workspace__slug=slug, pk=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, + ) diff --git a/apiserver/plane/utils/integrations/slack.py b/apiserver/plane/utils/integrations/slack.py new file mode 100644 index 00000000000..70f26e16091 --- /dev/null +++ b/apiserver/plane/utils/integrations/slack.py @@ -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 {} diff --git a/web/pages/installations/[provider]/index.tsx b/web/pages/installations/[provider]/index.tsx index ac8a2fc22ac..46556c9e6c8 100644 --- a/web/pages/installations/[provider]/index.tsx +++ b/web/pages/installations/[provider]/index.tsx @@ -27,53 +27,38 @@ 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(","); + const [workspaceSlug, projectId, integrationId] = state.toString().split(","); - if (!projectId) { - const payload = { - metadata: { - ...res, - }, - }; + if (!projectId) { + const payload = { + code, + }; - appInstallationService - .addInstallationApp(state.toString(), provider, payload) - .then((r) => { - window.opener = null; - window.open("", "_self"); - window.close(); - }) - .catch((err) => { - throw err?.response; - }); - } 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, - }; - appInstallationService - .addSlackChannel(workspaceSlug, projectId, integrationId, payload) - .then((r) => { - window.opener = null; - window.open("", "_self"); - window.close(); - }) - .catch((err) => { - throw err.response; - }); - } - }) - .catch((err) => { - console.log(err); - }); + appInstallationService + .addInstallationApp(state.toString(), provider, payload) + .then((r) => { + window.opener = null; + window.open("", "_self"); + window.close(); + }) + .catch((err) => { + throw err?.response; + }); + } else { + const payload = { + code, + }; + appInstallationService + .addSlackChannel(workspaceSlug, projectId, integrationId, payload) + .then((r) => { + window.opener = null; + window.open("", "_self"); + window.close(); + }) + .catch((err) => { + throw err.response; + }); + } } }, [state, installation_id, provider, code]); From c7af3404b9e5d2543640184b56e82d13509f5600 Mon Sep 17 00:00:00 2001 From: pablohashescobar Date: Mon, 6 Nov 2023 20:32:29 +0530 Subject: [PATCH 2/4] dev: add slack client id as configuration --- apiserver/plane/api/urls/__init__.py | 2 +- apiserver/plane/api/urls/{configuration.py => config.py} | 0 apiserver/plane/api/views/config.py | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) rename apiserver/plane/api/urls/{configuration.py => config.py} (100%) diff --git a/apiserver/plane/api/urls/__init__.py b/apiserver/plane/api/urls/__init__.py index 49c2b772e19..e4f3718f59c 100644 --- a/apiserver/plane/api/urls/__init__.py +++ b/apiserver/plane/api/urls/__init__.py @@ -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 diff --git a/apiserver/plane/api/urls/configuration.py b/apiserver/plane/api/urls/config.py similarity index 100% rename from apiserver/plane/api/urls/configuration.py rename to apiserver/plane/api/urls/config.py diff --git a/apiserver/plane/api/views/config.py b/apiserver/plane/api/views/config.py index f59ca04a053..687cb211c4d 100644 --- a/apiserver/plane/api/views/config.py +++ b/apiserver/plane/api/views/config.py @@ -30,4 +30,5 @@ def get(self, request): 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) From f67955ae81112b7dc9f3bf982677fdb5743dd2f9 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Mon, 6 Nov 2023 20:53:08 +0530 Subject: [PATCH 3/4] fix: clean up --- web/pages/api/slack-redirect.ts | 23 -------------------- web/pages/installations/[provider]/index.tsx | 7 +++--- web/services/app_installation.service.ts | 12 ---------- 3 files changed, 3 insertions(+), 39 deletions(-) delete mode 100644 web/pages/api/slack-redirect.ts diff --git a/web/pages/api/slack-redirect.ts b/web/pages/api/slack-redirect.ts deleted file mode 100644 index a6b8dbf4b79..00000000000 --- a/web/pages/api/slack-redirect.ts +++ /dev/null @@ -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" }); - } -} diff --git a/web/pages/installations/[provider]/index.tsx b/web/pages/installations/[provider]/index.tsx index 46556c9e6c8..243065a7c60 100644 --- a/web/pages/installations/[provider]/index.tsx +++ b/web/pages/installations/[provider]/index.tsx @@ -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) { @@ -33,10 +33,9 @@ const AppPostInstallation: NextPageWithLayout = () => { const payload = { code, }; - appInstallationService .addInstallationApp(state.toString(), provider, payload) - .then((r) => { + .then(() => { window.opener = null; window.open("", "_self"); window.close(); @@ -50,7 +49,7 @@ const AppPostInstallation: NextPageWithLayout = () => { }; appInstallationService .addSlackChannel(workspaceSlug, projectId, integrationId, payload) - .then((r) => { + .then(() => { window.opener = null; window.open("", "_self"); window.close(); diff --git a/web/services/app_installation.service.ts b/web/services/app_installation.service.ts index 2a7a4ea6af4..17972103640 100644 --- a/web/services/app_installation.service.ts +++ b/web/services/app_installation.service.ts @@ -60,16 +60,4 @@ export class AppInstallationService extends APIService { throw error?.response; }); } - - async getSlackAuthDetails(code: string): Promise { - const response = await this.request({ - method: "post", - url: "/api/slack-redirect", - data: { - code, - }, - }); - - return response.data; - } } From 7bb7eab9cbaa4c5ae59f81ea970d479463b16fc9 Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Mon, 6 Nov 2023 20:57:01 +0530 Subject: [PATCH 4/4] fix: added env to turbo --- turbo.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/turbo.json b/turbo.json index 62afa90bb16..7c3ccb81a7d 100644 --- a/turbo.json +++ b/turbo.json @@ -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": {