diff --git a/apps/app/components/integration/single-integration-card.tsx b/apps/app/components/integration/single-integration-card.tsx index 824ea0b9b..a2fe1061d 100644 --- a/apps/app/components/integration/single-integration-card.tsx +++ b/apps/app/components/integration/single-integration-card.tsx @@ -11,7 +11,7 @@ import IntegrationService from "services/integration"; import useToast from "hooks/use-toast"; import useIntegrationPopup from "hooks/use-integration-popup"; // ui -import { DangerButton, Loader, SecondaryButton } from "components/ui"; +import { DangerButton, Loader, PrimaryButton } from "components/ui"; // icons import GithubLogo from "public/services/github.png"; import SlackLogo from "public/services/slack.png"; @@ -33,7 +33,7 @@ const integrationDetails: { [key: string]: any } = { }, slack: { logo: SlackLogo, - installed: "Activate Slack integrations on individual projects to sync with specific cahnnels.", + installed: "Activate Slack integrations on individual projects to sync with specific channels.", notInstalled: "Connect with Slack with your Plane workspace to sync project issues.", }, }; @@ -139,9 +139,9 @@ export const SingleIntegrationCard: React.FC = ({ integration }) => { {deletingIntegration ? "Removing..." : "Remove installation"} ) : ( - + {isInstalling ? "Installing..." : "Add installation"} - + ) ) : ( diff --git a/apps/app/components/integration/slack/select-channel.tsx b/apps/app/components/integration/slack/select-channel.tsx index 2680cd847..621245dc8 100644 --- a/apps/app/components/integration/slack/select-channel.tsx +++ b/apps/app/components/integration/slack/select-channel.tsx @@ -1,85 +1,100 @@ -import React,{useState} from "react"; +import React, { useState, useEffect } from "react"; import { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; // services -import projectService from "services/project.service"; -import IntegrationService from "services/integration"; +import appinstallationsService from "services/app-installations.service"; // ui -import { DangerButton, Loader, SecondaryButton } from "components/ui"; +import { Loader } from "components/ui"; // hooks import useToast from "hooks/use-toast"; import useIntegrationPopup from "hooks/use-integration-popup"; // types -import { IAppIntegration, IWorkspaceIntegration } from "types"; +import { IWorkspaceIntegration } from "types"; // fetch-keys -import { WORKSPACE_INTEGRATIONS } from "constants/fetch-keys"; - - +import { SLACK_CHANNEL_INFO } from "constants/fetch-keys"; type Props = { integration: IWorkspaceIntegration; }; -export const SelectChannel: React.FC = ({ - integration, -}) => { - const [deletingIntegration, setDeletingIntegration] = useState(false); +export const SelectChannel: React.FC = ({ integration }) => { + const [deletingProjectSync, setDeletingProjectSync] = useState(false); - const router = useRouter(); - const { workspaceSlug } = router.query; + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; - const { setToastAlert } = useToast(); + const { startAuth } = useIntegrationPopup("slackChannel", integration.id); - const { startAuth, isConnecting: isInstalling } = useIntegrationPopup("slackChannel"); - - const { data: workspaceIntegrations } = useSWR( - workspaceSlug ? WORKSPACE_INTEGRATIONS(workspaceSlug as string) : null, - () => - workspaceSlug - ? IntegrationService.getWorkspaceIntegrationsList(workspaceSlug as string) - : null + const { data: projectIntegration } = useSWR( + workspaceSlug && projectId && integration.id + ? SLACK_CHANNEL_INFO(workspaceSlug as string, projectId as string) + : null, + () => + workspaceSlug && projectId && integration.id + ? appinstallationsService.getSlackChannelDetail( + workspaceSlug as string, + projectId as string, + integration.id as string + ) + : null ); - const isInstalled = workspaceIntegrations?.find( - (i: any) => i.integration_detail.id === integration.id - ); + useEffect(() => { + if (projectIntegration?.length > 0) { + setDeletingProjectSync(true); + } + if (projectIntegration?.length === 0) { + setDeletingProjectSync(false); + } + }, [projectIntegration]); - const handleRemoveIntegration = async () => { - if (!workspaceSlug || !integration || !workspaceIntegrations) return; - - const workspaceIntegrationId = workspaceIntegrations?.find( - (i) => i.integration === integration.id - )?.id; - - setDeletingIntegration(true); - - await IntegrationService.deleteWorkspaceIntegration( + const handleDelete = async () => { + if (projectIntegration.length === 0) return; + mutate(SLACK_CHANNEL_INFO, (prevData: any) => { + if (!prevData) return; + return prevData.id !== integration.id; + }).then(() => setDeletingProjectSync(false)); + appinstallationsService + .removeSlackChannel( workspaceSlug as string, - workspaceIntegrationId ?? "" + projectId as string, + integration.id as string, + projectIntegration?.[0]?.id ) - .then(() => { - setDeletingIntegration(false); - }) - .catch(() => { - setDeletingIntegration(false); - }); - }; + .catch((err) => console.log(err)); + }; + + const handleAuth = async () => { + await startAuth(); + setDeletingProjectSync(true); + }; return ( <> - {workspaceIntegrations ? ( - isInstalled ? ( - - {deletingIntegration ? "Removing..." : "Remove channel"} - - ) : ( - - {isInstalling ? "Adding..." : "Add channel"} - - ) + {projectIntegration ? ( + ) : ( diff --git a/apps/app/components/project/single-integration-card.tsx b/apps/app/components/project/single-integration-card.tsx index 8d361b0aa..88ea4002e 100644 --- a/apps/app/components/project/single-integration-card.tsx +++ b/apps/app/components/project/single-integration-card.tsx @@ -112,7 +112,7 @@ export const SingleIntegration: React.FC = ({ integration }) => { {integration.integration_detail.provider === "github" ? "Select GitHub repository to enable sync." : integration.integration_detail.provider === "slack" - ? "Select Slack channel to enable sync." + ? "Connect your slack channel to this project to get regular updates. Control which notification you want to receive" : null}

diff --git a/apps/app/constants/fetch-keys.ts b/apps/app/constants/fetch-keys.ts index aff1d99f4..1f4481c09 100644 --- a/apps/app/constants/fetch-keys.ts +++ b/apps/app/constants/fetch-keys.ts @@ -130,6 +130,10 @@ export const IMPORTER_SERVICES_LIST = (workspaceSlug: string) => export const GITHUB_REPOSITORY_INFO = (workspaceSlug: string, repoName: string) => `GITHUB_REPO_INFO_${workspaceSlug.toString().toUpperCase()}_${repoName.toUpperCase()}`; +// slack-project-integration +export const SLACK_CHANNEL_INFO = (workspaceSlug: string, projectId: string) => + `SLACK_CHANNEL_INFO_${workspaceSlug.toString().toUpperCase()}_${projectId.toUpperCase()}`; + // Calendar export const PROJECT_CALENDAR_ISSUES = (projectId: string) => `CALENDAR_ISSUES_${projectId.toUpperCase()}`; diff --git a/apps/app/hooks/use-integration-popup.tsx b/apps/app/hooks/use-integration-popup.tsx index 07d696a86..cd1ae6c0a 100644 --- a/apps/app/hooks/use-integration-popup.tsx +++ b/apps/app/hooks/use-integration-popup.tsx @@ -2,7 +2,7 @@ import { useRef, useState } from "react"; import { useRouter } from "next/router"; -const useIntegrationPopup = (provider: string | undefined) => { +const useIntegrationPopup = (provider: string | undefined, stateParams?: string) => { const [authLoader, setAuthLoader] = useState(false); const router = useRouter(); @@ -14,14 +14,13 @@ const useIntegrationPopup = (provider: string | undefined) => { }/installations/new?state=${workspaceSlug?.toString()}`, slack: `https://slack.com/oauth/v2/authorize?scope=chat%3Awrite%2Cim%3Ahistory%2Cim%3Awrite%2Clinks%3Aread%2Clinks%3Awrite%2Cusers%3Aread%2Cusers%3Aread.email&user_scope=&&client_id=${ process.env.NEXT_PUBLIC_SLACK_CLIENT_ID - }&state=${workspaceSlug?.toString()}&redirect_uri=https://53ff-2405-201-3005-e03e-bc47-b3f9-495d-414d.ngrok-free.app/installations/slack`, + }&state=${workspaceSlug?.toString()}`, slackChannel: `https://slack.com/oauth/v2/authorize?scope=incoming-webhook&client_id=${ process.env.NEXT_PUBLIC_SLACK_CLIENT_ID - }&state=${workspaceSlug?.toString()}_${projectId?.toString()}&redirect_uri=https://53ff-2405-201-3005-e03e-bc47-b3f9-495d-414d.ngrok-free.app/installations/slack/connect-project`, + }&state=${workspaceSlug?.toString()},${projectId?.toString()}${ + stateParams ? "," + stateParams : "" + }`, }; - - //&project=${projectId?.toString()} - const popup = useRef(); const checkPopup = () => { diff --git a/apps/app/pages/api/slack-redirect.ts b/apps/app/pages/api/slack-redirect.ts index 74fb1379e..e4a220bd5 100644 --- a/apps/app/pages/api/slack-redirect.ts +++ b/apps/app/pages/api/slack-redirect.ts @@ -17,5 +17,7 @@ export default async function handleSlackAuthorize(req: NextApiRequest, res: Nex }, }); - res.status(200).json(response.data); + // if (response?.data?.ok) + res.status(200).json(response.data); + // else res.status(404).json(response.data); } diff --git a/apps/app/pages/installations/[provider]/connect-project.tsx b/apps/app/pages/installations/[provider]/connect-project.tsx deleted file mode 100644 index 7bce47386..000000000 --- a/apps/app/pages/installations/[provider]/connect-project.tsx +++ /dev/null @@ -1,88 +0,0 @@ -import React, { useEffect } from "react"; - -import useSWR from "swr"; - -// services -import appinstallationsService from "services/app-installations.service"; -import IntegrationService from "services/integration"; -// components -import { Spinner } from "components/ui"; - -import { useRouter } from "next/router"; - -import { WORKSPACE_INTEGRATIONS } from "constants/fetch-keys"; - -interface IGithuPostInstallationProps { - installation_id: string; - setup_action: string; - state: string; - provider: string; - code: string; - projectId: string; -} - -// TODO:Change getServerSideProps to router.query -const AppPostInstallation = ({ - installation_id, - setup_action, - state, - provider, - code, - projectId, -}: IGithuPostInstallationProps) => { - - console.log(state, provider, code) - const { data: workspaceIntegrations } = useSWR( - state ? WORKSPACE_INTEGRATIONS(state as string) : null, - () => (state ? IntegrationService.getWorkspaceIntegrationsList(state as string) : null) - ); - console.log(workspaceIntegrations) - - const workspaceIntegrationId = workspaceIntegrations?.find( - (integration) => integration.integration_detail.provider === provider - ); - - console.log(workspaceIntegrationId); - - useEffect(() => { - if (provider && state && code) { - appinstallationsService - .getSlackAuthDetails(code) - .then((res) => { - const payload = { - metadata: { - ...res, - }, - }; - workspaceIntegrationId && appinstallationsService - .addSlackChannel(state, projectId, workspaceIntegrationId?.integration?.toString(), payload) - .then((r) => { - window.opener = null; - window.open("", "_self"); - window.close(); - }) - .catch((err) => { - throw err?.response; - }); - }) - .catch((err) => { - console.log(err); - }); - } - }, [state, installation_id, provider, code, projectId, workspaceIntegrationId]); - - return ( -
-

Installing. Please wait...

- -
- ); -}; - -export async function getServerSideProps(context: any) { - return { - props: context.query, - }; -} - -export default AppPostInstallation; diff --git a/apps/app/pages/installations/[provider]/index.tsx b/apps/app/pages/installations/[provider]/index.tsx index 50683c13b..269316b21 100644 --- a/apps/app/pages/installations/[provider]/index.tsx +++ b/apps/app/pages/installations/[provider]/index.tsx @@ -1,6 +1,10 @@ import React, { useEffect } from "react"; + // services import appinstallationsService from "services/app-installations.service"; + +import useToast from "hooks/use-toast"; + // components import { Spinner } from "components/ui"; @@ -22,6 +26,9 @@ const AppPostInstallation = ({ provider, code, }: IGithuPostInstallationProps) => { + + const { setToastAlert } = useToast(); + useEffect(() => { if (provider === "github" && state && installation_id) { appinstallationsService @@ -35,29 +42,53 @@ const AppPostInstallation = ({ console.log(err); }); } else if (provider === "slack" && state && code) { - appinstallationsService - .getSlackAuthDetails(code) - .then((res) => { - const payload = { - metadata: { - ...res, - }, - }; + appinstallationsService + .getSlackAuthDetails(code) + .then((res) => { + const [workspaceSlug, projectId, integrationId] = state.split(","); - appinstallationsService - .addInstallationApp(state, provider, 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 = { + metadata: { + ...res, + }, + }; + + appinstallationsService + .addInstallationApp(state, 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, + }; + appinstallationsService + .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); + }); } }, [state, installation_id, provider, code]); diff --git a/apps/app/services/app-installations.service.ts b/apps/app/services/app-installations.service.ts index 6ab12bb8a..8ee04dcfe 100644 --- a/apps/app/services/app-installations.service.ts +++ b/apps/app/services/app-installations.service.ts @@ -1,11 +1,6 @@ // services import axios from "axios"; import APIService from "services/api.service"; -import IntegrationService from "services/integration"; - -import { - WORKSPACE_INTEGRATIONS, -} from "constants/fetch-keys"; const { NEXT_PUBLIC_API_BASE_URL } = process.env; @@ -33,6 +28,26 @@ class AppInstallationsService extends APIService { }); } + async getSlackChannelDetail(workspaceSlug: string, projectId: string, integrationId: string | null | undefined): Promise { + return this.get( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/workspace-integrations/${integrationId}/project-slack-sync/` + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + + async removeSlackChannel(workspaceSlug: string, projectId: string, integrationId: string | null | undefined, slackSyncId: string | undefined): Promise { + return this.delete( + `/api/workspaces/${workspaceSlug}/projects/${projectId}/workspace-integrations/${integrationId}/project-slack-sync/${slackSyncId}` + ) + .then((response) => response?.data) + .catch((error) => { + throw error?.response; + }); + } + async getSlackAuthDetails(code: string): Promise { const response = await axios({ method: "post",