From 0619f1b6d1d7bf8d73605370baec522cbb5c08a1 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Mon, 5 Aug 2024 13:37:11 +0530 Subject: [PATCH] [WEB-2103]: chore: Intercom integration (#5295) * fix: intecom sdk integration * dev: integrated intercom in god-mode * dev: intercom default value true * dev: updated intercom keys in intercom provider * chore: added restriction values --------- Co-authored-by: sriram veeraghanta --- admin/app/general/form.tsx | 19 ++++- admin/app/general/intercom.tsx | 82 +++++++++++++++++++ admin/app/general/page.tsx | 2 +- admin/core/components/login/sign-in-form.tsx | 2 - apiserver/plane/license/api/views/instance.py | 15 ++++ .../management/commands/configure_instance.py | 19 ++++- apiserver/plane/utils/constants.py | 5 ++ packages/types/src/instance/base.d.ts | 10 ++- web/app/layout.tsx | 2 +- web/app/provider.tsx | 14 ++-- .../workspace/sidebar/help-section.tsx | 5 +- web/core/hooks/store/index.ts | 36 ++++---- web/core/hooks/store/pages/index.ts | 2 + web/core/lib/intercom-provider.tsx | 33 ++++++++ web/next.config.js | 5 ++ web/package.json | 1 + web/public/sw.js.map | 2 +- yarn.lock | 7 +- 18 files changed, 221 insertions(+), 40 deletions(-) create mode 100644 admin/app/general/intercom.tsx create mode 100644 web/core/hooks/store/pages/index.ts create mode 100644 web/core/lib/intercom-provider.tsx diff --git a/admin/app/general/form.tsx b/admin/app/general/form.tsx index 310153784..e83f48d9f 100644 --- a/admin/app/general/form.tsx +++ b/admin/app/general/form.tsx @@ -9,6 +9,7 @@ import { IInstance, IInstanceAdmin } from "@plane/types"; import { Button, Input, TOAST_TYPE, ToggleSwitch, setToast } from "@plane/ui"; // components import { ControllerInput } from "@/components/common"; +import { IntercomConfig } from "./intercom"; // hooks import { useInstance } from "@/hooks/store"; @@ -20,11 +21,13 @@ export interface IGeneralConfigurationForm { export const GeneralConfigurationForm: FC = observer((props) => { const { instance, instanceAdmins } = props; // hooks - const { updateInstanceInfo } = useInstance(); + const { instanceConfigurations, updateInstanceInfo, updateInstanceConfigurations } = useInstance(); + // form data const { handleSubmit, control, + watch, formState: { errors, isSubmitting }, } = useForm>({ defaultValues: { @@ -36,7 +39,16 @@ export const GeneralConfigurationForm: FC = observer( const onSubmit = async (formData: Partial) => { const payload: Partial = { ...formData }; - console.log("payload", payload); + // update the intercom configuration + const isIntercomEnabled = + instanceConfigurations?.find((config) => config.key === "IS_INTERCOM_ENABLED")?.value === "1"; + if (!payload.is_telemetry_enabled && isIntercomEnabled) { + try { + await updateInstanceConfigurations({ IS_INTERCOM_ENABLED: "0" }); + } catch (error) { + console.error(error); + } + } await updateInstanceInfo(payload) .then(() => @@ -93,7 +105,8 @@ export const GeneralConfigurationForm: FC = observer(
-
Telemetry
+
Chat + telemetry
+
diff --git a/admin/app/general/intercom.tsx b/admin/app/general/intercom.tsx new file mode 100644 index 000000000..16002503a --- /dev/null +++ b/admin/app/general/intercom.tsx @@ -0,0 +1,82 @@ +"use client"; + +import { FC, FormEvent, useEffect, useState } from "react"; +import { observer } from "mobx-react"; +import { MessageSquare } from "lucide-react"; +import useSWR from "swr"; +import { IFormattedInstanceConfiguration } from "@plane/types"; +import { Button, Input, ToggleSwitch } from "@plane/ui"; +// hooks +import { useInstance } from "@/hooks/store"; + +type TIntercomConfig = { + isTelemetryEnabled: boolean; +}; + +export const IntercomConfig: FC = observer((props) => { + const { isTelemetryEnabled } = props; + // hooks + const { instanceConfigurations, instance, updateInstanceConfigurations, fetchInstanceConfigurations } = useInstance(); + // states + const [isSubmitting, setIsSubmitting] = useState(false); + + // derived values + const isIntercomEnabled = isTelemetryEnabled + ? instanceConfigurations + ? instanceConfigurations?.find((config) => config.key === "IS_INTERCOM_ENABLED")?.value === "1" + ? true + : false + : undefined + : false; + + const { isLoading } = useSWR(isTelemetryEnabled ? "INSTANCE_CONFIGURATIONS" : null, () => + isTelemetryEnabled ? fetchInstanceConfigurations() : null + ); + + const initialLoader = isLoading && isIntercomEnabled === undefined; + + const submitInstanceConfigurations = async (payload: Partial) => { + try { + await updateInstanceConfigurations(payload); + } catch (error) { + console.error(error); + } finally { + setIsSubmitting(false); + } + }; + + const enableIntercomConfig = () => { + submitInstanceConfigurations({ IS_INTERCOM_ENABLED: isIntercomEnabled ? "0" : "1" }); + }; + + return ( + <> +
+
+
+
+ +
+
+ +
+
Talk to Plane
+
+ Let your members chat with us via Intercom or another service. Toggling Telemetry off turns this off + automatically. +
+
+ +
+ +
+
+
+ + ); +}); diff --git a/admin/app/general/page.tsx b/admin/app/general/page.tsx index ba048f9f7..f0d32f261 100644 --- a/admin/app/general/page.tsx +++ b/admin/app/general/page.tsx @@ -7,7 +7,7 @@ import { GeneralConfigurationForm } from "./form"; function GeneralPage() { const { instance, instanceAdmins } = useInstance(); - console.log("instance", instance); + return ( <>
diff --git a/admin/core/components/login/sign-in-form.tsx b/admin/core/components/login/sign-in-form.tsx index 45d448d12..72aeae50d 100644 --- a/admin/core/components/login/sign-in-form.tsx +++ b/admin/core/components/login/sign-in-form.tsx @@ -57,8 +57,6 @@ export const InstanceSignInForm: FC = (props) => { const handleFormChange = (key: keyof TFormData, value: string | boolean) => setFormData((prev) => ({ ...prev, [key]: value })); - console.log("csrfToken", csrfToken); - useEffect(() => { if (csrfToken === undefined) authService.requestCSRFToken().then((data) => data?.csrf_token && setCsrfToken(data.csrf_token)); diff --git a/apiserver/plane/license/api/views/instance.py b/apiserver/plane/license/api/views/instance.py index 4f021963b..9df727607 100644 --- a/apiserver/plane/license/api/views/instance.py +++ b/apiserver/plane/license/api/views/instance.py @@ -66,6 +66,8 @@ class InstanceEndpoint(BaseAPIView): POSTHOG_HOST, UNSPLASH_ACCESS_KEY, OPENAI_API_KEY, + IS_INTERCOM_ENABLED, + INTERCOM_APP_ID, ) = get_configuration_value( [ { @@ -116,6 +118,15 @@ class InstanceEndpoint(BaseAPIView): "key": "OPENAI_API_KEY", "default": os.environ.get("OPENAI_API_KEY", ""), }, + # Intercom settings + { + "key": "IS_INTERCOM_ENABLED", + "default": os.environ.get("IS_INTERCOM_ENABLED", "1"), + }, + { + "key": "INTERCOM_APP_ID", + "default": os.environ.get("INTERCOM_APP_ID", ""), + }, ] ) @@ -151,6 +162,10 @@ class InstanceEndpoint(BaseAPIView): # is smtp configured data["is_smtp_configured"] = bool(EMAIL_HOST) + # Intercom settings + data["is_intercom_enabled"] = IS_INTERCOM_ENABLED == "1" + data["intercom_app_id"] = INTERCOM_APP_ID + # Base URL data["admin_base_url"] = settings.ADMIN_BASE_URL data["space_base_url"] = settings.SPACE_BASE_URL diff --git a/apiserver/plane/license/management/commands/configure_instance.py b/apiserver/plane/license/management/commands/configure_instance.py index 7f61024f9..ba6a57d4b 100644 --- a/apiserver/plane/license/management/commands/configure_instance.py +++ b/apiserver/plane/license/management/commands/configure_instance.py @@ -143,6 +143,19 @@ class Command(BaseCommand): "category": "UNSPLASH", "is_encrypted": True, }, + # intercom settings + { + "key": "IS_INTERCOM_ENABLED", + "value": os.environ.get("IS_INTERCOM_ENABLED", "1"), + "category": "INTERCOM", + "is_encrypted": False, + }, + { + "key": "INTERCOM_APP_ID", + "value": os.environ.get("INTERCOM_APP_ID", ""), + "category": "INTERCOM", + "is_encrypted": False, + }, ] for item in config_keys: @@ -265,7 +278,11 @@ class Command(BaseCommand): ] ) ) - if bool(GITLAB_HOST) and bool(GITLAB_CLIENT_ID) and bool(GITLAB_CLIENT_SECRET): + if ( + bool(GITLAB_HOST) + and bool(GITLAB_CLIENT_ID) + and bool(GITLAB_CLIENT_SECRET) + ): value = "1" else: value = "0" diff --git a/apiserver/plane/utils/constants.py b/apiserver/plane/utils/constants.py index 635268628..a4d1e1ea6 100644 --- a/apiserver/plane/utils/constants.py +++ b/apiserver/plane/utils/constants.py @@ -27,4 +27,9 @@ RESTRICTED_WORKSPACE_SLUGS = [ "channels", "upgrade", "billing", + "sign-in", + "sign-up", + "signin", + "signup", + "config", ] diff --git a/packages/types/src/instance/base.d.ts b/packages/types/src/instance/base.d.ts index 1332102b7..8095f4e01 100644 --- a/packages/types/src/instance/base.d.ts +++ b/packages/types/src/instance/base.d.ts @@ -52,6 +52,9 @@ export interface IInstanceConfig { app_base_url: string | undefined; space_base_url: string | undefined; admin_base_url: string | undefined; + // intercom + is_intercom_enabled: boolean; + intercom_app_id: string | undefined; } export interface IInstanceAdmin { @@ -66,11 +69,16 @@ export interface IInstanceAdmin { user_detail: IUserLite; } +export type TInstanceIntercomConfigurationKeys = + | "IS_INTERCOM_ENABLED" + | "INTERCOM_APP_ID"; + export type TInstanceConfigurationKeys = | TInstanceAIConfigurationKeys | TInstanceEmailConfigurationKeys | TInstanceImageConfigurationKeys - | TInstanceAuthenticationKeys; + | TInstanceAuthenticationKeys + | TInstanceIntercomConfigurationKeys; export interface IInstanceConfiguration { id: string; diff --git a/web/app/layout.tsx b/web/app/layout.tsx index ead630ee9..50b7b3799 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -46,7 +46,7 @@ export default function RootLayout({ children }: { children: React.ReactNode }) - + diff --git a/web/app/provider.tsx b/web/app/provider.tsx index 9ae53330c..12b6a09f8 100644 --- a/web/app/provider.tsx +++ b/web/app/provider.tsx @@ -19,7 +19,7 @@ import { InstanceWrapper } from "@/lib/wrappers"; // dynamic imports const StoreWrapper = dynamic(() => import("@/lib/wrappers/store-wrapper"), { ssr: false }); const PostHogProvider = dynamic(() => import("@/lib/posthog-provider"), { ssr: false }); -const CrispWrapper = dynamic(() => import("@/lib/wrappers/crisp-wrapper"), { ssr: false }); +const IntercomProvider = dynamic(() => import("@/lib/intercom-provider"), { ssr: false }); export interface IAppProvider { children: ReactNode; @@ -39,15 +39,15 @@ export const AppProvider: FC = (props) => { - - - + + + {children} - - - + + + diff --git a/web/core/components/workspace/sidebar/help-section.tsx b/web/core/components/workspace/sidebar/help-section.tsx index a14b2b92b..864a57f3a 100644 --- a/web/core/components/workspace/sidebar/help-section.tsx +++ b/web/core/components/workspace/sidebar/help-section.tsx @@ -10,7 +10,7 @@ import { DiscordIcon, GithubIcon, Tooltip } from "@plane/ui"; // helpers import { cn } from "@/helpers/common.helper"; // hooks -import { useAppTheme, useCommandPalette } from "@/hooks/store"; +import { useAppTheme, useCommandPalette, useInstance } from "@/hooks/store"; import useOutsideClickDetector from "@/hooks/use-outside-click-detector"; import { usePlatformOS } from "@/hooks/use-platform-os"; // components @@ -44,6 +44,7 @@ export const SidebarHelpSection: React.FC = observer( const { sidebarCollapsed, toggleSidebar } = useAppTheme(); const { toggleShortcutModal } = useCommandPalette(); const { isMobile } = usePlatformOS(); + const { config } = useInstance(); // states const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false); // refs @@ -148,7 +149,7 @@ export const SidebarHelpSection: React.FC = observer( ))} - {process.env.NEXT_PUBLIC_CRISP_ID && ( + {config?.intercom_app_id && config?.is_intercom_enabled && (