Skip to content

Commit

Permalink
[WEB-2103]: chore: Intercom integration (makeplane#5295)
Browse files Browse the repository at this point in the history
* 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 <veeraghanta.sriram@gmail.com>
  • Loading branch information
gurusainath and sriramveeraghanta authored Aug 5, 2024
1 parent 34820ee commit 0619f1b
Show file tree
Hide file tree
Showing 18 changed files with 221 additions and 40 deletions.
19 changes: 16 additions & 3 deletions admin/app/general/form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -20,11 +21,13 @@ export interface IGeneralConfigurationForm {
export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = 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<Partial<IInstance>>({
defaultValues: {
Expand All @@ -36,7 +39,16 @@ export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = observer(
const onSubmit = async (formData: Partial<IInstance>) => {
const payload: Partial<IInstance> = { ...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(() =>
Expand Down Expand Up @@ -93,7 +105,8 @@ export const GeneralConfigurationForm: FC<IGeneralConfigurationForm> = observer(
</div>

<div className="space-y-3">
<div className="text-lg font-medium">Telemetry</div>
<div className="text-lg font-medium">Chat + telemetry</div>
<IntercomConfig isTelemetryEnabled={watch("is_telemetry_enabled") ?? false} />
<div className="flex items-center gap-14 px-4 py-3 border border-custom-border-200 rounded">
<div className="grow flex items-center gap-4">
<div className="shrink-0">
Expand Down
82 changes: 82 additions & 0 deletions admin/app/general/intercom.tsx
Original file line number Diff line number Diff line change
@@ -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<TIntercomConfig> = observer((props) => {
const { isTelemetryEnabled } = props;
// hooks
const { instanceConfigurations, instance, updateInstanceConfigurations, fetchInstanceConfigurations } = useInstance();
// states
const [isSubmitting, setIsSubmitting] = useState<boolean>(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<IFormattedInstanceConfiguration>) => {
try {
await updateInstanceConfigurations(payload);
} catch (error) {
console.error(error);
} finally {
setIsSubmitting(false);
}
};

const enableIntercomConfig = () => {
submitInstanceConfigurations({ IS_INTERCOM_ENABLED: isIntercomEnabled ? "0" : "1" });
};

return (
<>
<div className="flex items-center gap-14 px-4 py-3 border border-custom-border-200 rounded">
<div className="grow flex items-center gap-4">
<div className="shrink-0">
<div className="flex items-center justify-center w-10 h-10 bg-custom-background-80 rounded-full">
<MessageSquare className="w-6 h-6 text-custom-text-300/80 p-0.5" />
</div>
</div>

<div className="grow">
<div className="text-sm font-medium text-custom-text-100 leading-5">Talk to Plane</div>
<div className="text-xs font-normal text-custom-text-300 leading-5">
Let your members chat with us via Intercom or another service. Toggling Telemetry off turns this off
automatically.
</div>
</div>

<div className="ml-auto">
<ToggleSwitch
value={isIntercomEnabled ? true : false}
onChange={enableIntercomConfig}
size="sm"
disabled={!isTelemetryEnabled || isSubmitting || initialLoader}
/>
</div>
</div>
</div>
</>
);
});
2 changes: 1 addition & 1 deletion admin/app/general/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { GeneralConfigurationForm } from "./form";

function GeneralPage() {
const { instance, instanceAdmins } = useInstance();
console.log("instance", instance);

return (
<>
<div className="relative container mx-auto w-full h-full p-4 py-4 space-y-6 flex flex-col">
Expand Down
2 changes: 0 additions & 2 deletions admin/core/components/login/sign-in-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down
15 changes: 15 additions & 0 deletions apiserver/plane/license/api/views/instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ def get(self, request):
POSTHOG_HOST,
UNSPLASH_ACCESS_KEY,
OPENAI_API_KEY,
IS_INTERCOM_ENABLED,
INTERCOM_APP_ID,
) = get_configuration_value(
[
{
Expand Down Expand Up @@ -116,6 +118,15 @@ def get(self, request):
"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", ""),
},
]
)

Expand Down Expand Up @@ -151,6 +162,10 @@ def get(self, request):
# 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,19 @@ def handle(self, *args, **options):
"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:
Expand Down Expand Up @@ -265,7 +278,11 @@ def handle(self, *args, **options):
]
)
)
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"
Expand Down
5 changes: 5 additions & 0 deletions apiserver/plane/utils/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,9 @@
"channels",
"upgrade",
"billing",
"sign-in",
"sign-up",
"signin",
"signup",
"config",
]
10 changes: 9 additions & 1 deletion packages/types/src/instance/base.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion web/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
<meta name="apple-mobile-web-app-title" content={SITE_NAME} />
<meta name="format-detection" content="telephone=no" />
<meta name="mobile-web-app-capable" content="yes" />
<link rel="apple-touch-icon" href="/icons/touch-icon-iphone.png" />
<link rel="apple-touch-icon" href="/icons/icon-512x512.png" />
<link rel="apple-touch-icon" sizes="152x152" href="/icons/icon-152x152.png" />
<link rel="apple-touch-icon" sizes="180x180" href="/icons/icon-180x180.png" />
<link rel="apple-touch-icon" sizes="512x512" href="/icons/icon-512x512.png" />
Expand Down
14 changes: 7 additions & 7 deletions web/app/provider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -39,15 +39,15 @@ export const AppProvider: FC<IAppProvider> = (props) => {
<StoreProvider>
<ThemeProvider themes={["light", "dark", "light-contrast", "dark-contrast", "custom"]} defaultTheme="system">
<ToastWithTheme />
<InstanceWrapper>
<StoreWrapper>
<CrispWrapper>
<StoreWrapper>
<InstanceWrapper>
<IntercomProvider>
<PostHogProvider>
<SWRConfig value={SWR_CONFIG}>{children}</SWRConfig>
</PostHogProvider>
</CrispWrapper>
</StoreWrapper>
</InstanceWrapper>
</IntercomProvider>
</InstanceWrapper>
</StoreWrapper>
</ThemeProvider>
</StoreProvider>
</>
Expand Down
5 changes: 3 additions & 2 deletions web/core/components/workspace/sidebar/help-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -44,6 +44,7 @@ export const SidebarHelpSection: React.FC<WorkspaceHelpSectionProps> = observer(
const { sidebarCollapsed, toggleSidebar } = useAppTheme();
const { toggleShortcutModal } = useCommandPalette();
const { isMobile } = usePlatformOS();
const { config } = useInstance();
// states
const [isNeedHelpOpen, setIsNeedHelpOpen] = useState(false);
// refs
Expand Down Expand Up @@ -148,7 +149,7 @@ export const SidebarHelpSection: React.FC<WorkspaceHelpSectionProps> = observer(
</span>
</Link>
))}
{process.env.NEXT_PUBLIC_CRISP_ID && (
{config?.intercom_app_id && config?.is_intercom_enabled && (
<button
type="button"
onClick={handleCrispWindowShow}
Expand Down
36 changes: 16 additions & 20 deletions web/core/hooks/store/index.ts
Original file line number Diff line number Diff line change
@@ -1,36 +1,32 @@
export * from "./estimates";
export * from "./notifications";
export * from "./pages";
export * from "./use-app-theme";
export * from "./use-calendar-view";
export * from "./use-cycle-filter";
export * from "./use-command-palette";
export * from "./use-cycle";
export * from "./use-event-tracker";
export * from "./use-cycle-filter";
export * from "./use-dashboard";
export * from "./use-event-tracker";
export * from "./use-global-view";
export * from "./use-inbox-issues";
export * from "./use-instance";
export * from "./use-issue-detail";
export * from "./use-issues";
export * from "./use-kanban-view";
export * from "./use-label";
export * from "./use-member";
export * from "./use-mention";
export * from "./use-module";
export * from "./use-multiple-select-store";

export * from "./pages/use-project-page";
export * from "./pages/use-page";

export * from "./use-module-filter";
export * from "./use-multiple-select-store";
export * from "./use-project";
export * from "./use-project-filter";
export * from "./use-project-inbox";
export * from "./use-project-publish";
export * from "./use-project-state";
export * from "./use-project-view";
export * from "./use-project";
export * from "./use-router-params";
export * from "./use-webhook";
export * from "./use-workspace";
export * from "./use-issues";
export * from "./use-kanban-view";
export * from "./use-issue-detail";
// project inbox
export * from "./use-project-inbox";
export * from "./use-inbox-issues";
export * from "./user";
export * from "./use-instance";
export * from "./use-app-theme";
export * from "./use-command-palette";
export * from "./use-router-params";
export * from "./estimates";
export * from "./notifications";
2 changes: 2 additions & 0 deletions web/core/hooks/store/pages/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./use-page";
export * from "./use-project-page";
Loading

0 comments on commit 0619f1b

Please sign in to comment.