Skip to content
Merged
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
2 changes: 1 addition & 1 deletion apiserver/plane/api/urls/__init__.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down
1 change: 1 addition & 0 deletions apiserver/plane/api/views/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
15 changes: 11 additions & 4 deletions apiserver/plane/api/views/integration/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Python improts
import uuid

import requests
# Django imports
from django.contrib.auth.hashers import make_password

Expand All @@ -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
Expand Down Expand Up @@ -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}
Expand Down
44 changes: 33 additions & 11 deletions apiserver/plane/api/views/integration/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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,
)
20 changes: 20 additions & 0 deletions apiserver/plane/utils/integrations/slack.py
Original file line number Diff line number Diff line change
@@ -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 {}
3 changes: 2 additions & 1 deletion turbo.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
23 changes: 0 additions & 23 deletions web/pages/api/slack-redirect.ts

This file was deleted.

78 changes: 31 additions & 47 deletions web/pages/installations/[provider]/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -27,53 +27,37 @@ 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,
},
};
const [workspaceSlug, projectId, integrationId] = state.toString().split(",");

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);
});
if (!projectId) {
const payload = {
code,
};
appInstallationService
.addInstallationApp(state.toString(), provider, payload)
.then(() => {
window.opener = null;
window.open("", "_self");
window.close();
})
.catch((err) => {
throw err?.response;
});
} else {
const payload = {
code,
};
appInstallationService
.addSlackChannel(workspaceSlug, projectId, integrationId, payload)
.then(() => {
window.opener = null;
window.open("", "_self");
window.close();
})
.catch((err) => {
throw err.response;
});
}
}
}, [state, installation_id, provider, code]);

Expand Down
12 changes: 0 additions & 12 deletions web/services/app_installation.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}