diff --git a/airbyte-webapp/.storybook/withProvider.tsx b/airbyte-webapp/.storybook/withProvider.tsx index 8f5d0e54c2e80..83cf558677a8c 100644 --- a/airbyte-webapp/.storybook/withProvider.tsx +++ b/airbyte-webapp/.storybook/withProvider.tsx @@ -12,17 +12,14 @@ import { FeatureService } from "../src/hooks/services/Feature"; import { ConfigServiceProvider, defaultConfig } from "../src/config"; import { DocumentationPanelProvider } from "../src/views/Connector/ConnectorDocumentationLayout/DocumentationPanelContext"; import { ServicesProvider } from "../src/core/servicesProvider"; -import { analyticsServiceContext, AnalyticsServiceProviderValue } from "../src/hooks/services/Analytics"; +import { analyticsServiceContext } from "../src/hooks/services/Analytics"; +import type { AnalyticsService } from "../src/core/analytics"; -const AnalyticsContextMock: AnalyticsServiceProviderValue = { - analyticsContext: {}, - setContext: () => {}, - addContextProps: () => {}, - removeContextProps: () => {}, - service: { +const analyticsContextMock: AnalyticsService = { track: () => {}, - }, -} as unknown as AnalyticsServiceProviderValue; + setContext: () => {}, + removeFromContext: () => {}, +} as unknown as AnalyticsService; const queryClient = new QueryClient({ defaultOptions: { @@ -35,7 +32,7 @@ const queryClient = new QueryClient({ export const withProviders = (getStory) => ( - + diff --git a/airbyte-webapp/src/core/analytics/AnalyticsService.test.ts b/airbyte-webapp/src/core/analytics/AnalyticsService.test.ts index de3ab037a67c9..2b75daae75ac6 100644 --- a/airbyte-webapp/src/core/analytics/AnalyticsService.test.ts +++ b/airbyte-webapp/src/core/analytics/AnalyticsService.test.ts @@ -26,13 +26,13 @@ describe("AnalyticsService", () => { }); it("should send events to segment", () => { - const service = new AnalyticsService({}); + const service = new AnalyticsService(); service.track(Namespace.CONNECTION, Action.CREATE, {}); expect(window.analytics.track).toHaveBeenCalledWith("Airbyte.UI.Connection.Create", expect.anything()); }); it("should send version and environment for prod", () => { - const service = new AnalyticsService({}, "0.42.13"); + const service = new AnalyticsService("0.42.13"); service.track(Namespace.CONNECTION, Action.CREATE, {}); expect(window.analytics.track).toHaveBeenCalledWith( expect.anything(), @@ -41,7 +41,7 @@ describe("AnalyticsService", () => { }); it("should send version and environment for dev", () => { - const service = new AnalyticsService({}, "dev"); + const service = new AnalyticsService("dev"); service.track(Namespace.CONNECTION, Action.CREATE, {}); expect(window.analytics.track).toHaveBeenCalledWith( expect.anything(), @@ -50,7 +50,7 @@ describe("AnalyticsService", () => { }); it("should pass parameters to segment event", () => { - const service = new AnalyticsService({}); + const service = new AnalyticsService(); service.track(Namespace.CONNECTION, Action.CREATE, { actionDescription: "Created new connection" }); expect(window.analytics.track).toHaveBeenCalledWith( expect.anything(), @@ -59,7 +59,8 @@ describe("AnalyticsService", () => { }); it("should pass context parameters to segment event", () => { - const service = new AnalyticsService({ context: 42 }); + const service = new AnalyticsService(); + service.setContext({ context: 42 }); service.track(Namespace.CONNECTION, Action.CREATE, { actionDescription: "Created new connection" }); expect(window.analytics.track).toHaveBeenCalledWith( expect.anything(), diff --git a/airbyte-webapp/src/core/analytics/AnalyticsService.ts b/airbyte-webapp/src/core/analytics/AnalyticsService.ts index 90cebc58e939a..6c0e8f0fb02b6 100644 --- a/airbyte-webapp/src/core/analytics/AnalyticsService.ts +++ b/airbyte-webapp/src/core/analytics/AnalyticsService.ts @@ -1,10 +1,25 @@ import { Action, EventParams, Namespace } from "./types"; +type Context = Record; + export class AnalyticsService { - constructor(private context: Record, private version?: string) {} + private context: Context = {}; + + constructor(private version?: string) {} private getSegmentAnalytics = (): SegmentAnalytics.AnalyticsJS | undefined => window.analytics; + public setContext(context: Context) { + this.context = { + ...this.context, + ...context, + }; + } + + public removeFromContext(...keys: string[]) { + keys.forEach((key) => delete this.context[key]); + } + alias = (newId: string): void => this.getSegmentAnalytics()?.alias?.(newId); page = (name: string): void => this.getSegmentAnalytics()?.page?.(name, { ...this.context }); diff --git a/airbyte-webapp/src/hooks/services/Analytics/__mocks__/useAnalyticsService.tsx b/airbyte-webapp/src/hooks/services/Analytics/__mocks__/useAnalyticsService.tsx index 32d47f6d3da00..59f623af77899 100644 --- a/airbyte-webapp/src/hooks/services/Analytics/__mocks__/useAnalyticsService.tsx +++ b/airbyte-webapp/src/hooks/services/Analytics/__mocks__/useAnalyticsService.tsx @@ -8,5 +8,7 @@ export const useAnalyticsService = (): AnalyticsService => { track: jest.fn(), identify: jest.fn(), group: jest.fn(), + setContext: jest.fn(), + removeFromContext: jest.fn(), } as unknown as AnalyticsService; }; diff --git a/airbyte-webapp/src/hooks/services/Analytics/useAnalyticsService.tsx b/airbyte-webapp/src/hooks/services/Analytics/useAnalyticsService.tsx index a038a0072e0a5..eb9e0f060ad57 100644 --- a/airbyte-webapp/src/hooks/services/Analytics/useAnalyticsService.tsx +++ b/airbyte-webapp/src/hooks/services/Analytics/useAnalyticsService.tsx @@ -1,62 +1,26 @@ -import React, { useContext, useEffect, useMemo } from "react"; -import { useMap } from "react-use"; +import React, { useContext, useEffect, useRef } from "react"; +import { useConfig } from "config"; import { AnalyticsService } from "core/analytics/AnalyticsService"; type AnalyticsContext = Record; -export interface AnalyticsServiceProviderValue { - analyticsContext: AnalyticsContext; - setContext: (ctx: AnalyticsContext) => void; - addContextProps: (props: AnalyticsContext) => void; - removeContextProps: (props: string[]) => void; - service: AnalyticsService; -} - -export const analyticsServiceContext = React.createContext(null); - -const AnalyticsServiceProvider = ({ - children, - version, - initialContext = {}, -}: { - children: React.ReactNode; - version?: string; - initialContext?: AnalyticsContext; -}) => { - const [analyticsContext, { set, setAll, remove }] = useMap(initialContext); - - const analyticsService: AnalyticsService = useMemo( - () => new AnalyticsService(analyticsContext, version), - [version, analyticsContext] - ); +export const analyticsServiceContext = React.createContext(null); - const handleAddContextProps = (props: AnalyticsContext) => { - Object.entries(props).forEach((value) => set(...value)); - }; +const AnalyticsServiceProvider: React.FC> = ({ children }) => { + const { version } = useConfig(); + const analyticsService = useRef(); - const handleRemoveContextProps = (props: string[]) => props.forEach(remove); + if (!analyticsService.current) { + analyticsService.current = new AnalyticsService(version); + } return ( - - {children} - + {children} ); }; export const useAnalyticsService = (): AnalyticsService => { - return useAnalytics().service; -}; - -export const useAnalytics = (): AnalyticsServiceProviderValue => { const analyticsContext = useContext(analyticsServiceContext); if (!analyticsContext) { @@ -86,15 +50,15 @@ export const useTrackPage = (page: string): void => { }; export const useAnalyticsRegisterValues = (props?: AnalyticsContext | null): void => { - const { addContextProps, removeContextProps } = useAnalytics(); + const service = useAnalyticsService(); useEffect(() => { if (!props) { return; } - addContextProps(props); - return () => removeContextProps(Object.keys(props)); + service.setContext(props); + return () => service.removeFromContext(...Object.keys(props)); // eslint-disable-next-line react-hooks/exhaustive-deps }, [props]); diff --git a/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx b/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx index ac74dd5f3254a..bd4bac8d32370 100644 --- a/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx +++ b/airbyte-webapp/src/packages/cloud/cloudRoutes.tsx @@ -13,7 +13,6 @@ import { OnboardingServiceProvider } from "hooks/services/Onboarding"; import { useQuery } from "hooks/useQuery"; import { useExperimentSpeedyConnection } from "packages/cloud/components/experiments/SpeedyConnection/hooks/useExperimentSpeedyConnection"; import { useAuthService } from "packages/cloud/services/auth/AuthService"; -import { useIntercom } from "packages/cloud/services/thirdParty/intercom/useIntercom"; import { Auth } from "packages/cloud/views/auth"; import { CreditsPage } from "packages/cloud/views/credits"; import MainView from "packages/cloud/views/layout/MainView"; @@ -114,7 +113,6 @@ const MainRoutes: React.FC = () => { const MainViewRoutes = () => { useApiHealthPoll(); - useIntercom(); const query = useQuery<{ from: string }>(); return ( diff --git a/airbyte-webapp/src/packages/cloud/services/thirdParty/intercom/useIntercom.ts b/airbyte-webapp/src/packages/cloud/services/thirdParty/intercom/useIntercom.ts index 49f92d63b4e5b..d49962e197d8b 100644 --- a/airbyte-webapp/src/packages/cloud/services/thirdParty/intercom/useIntercom.ts +++ b/airbyte-webapp/src/packages/cloud/services/thirdParty/intercom/useIntercom.ts @@ -1,14 +1,14 @@ import { useEffect } from "react"; import { useIntercom as useIntercomProvider, IntercomContextValues } from "react-use-intercom"; -import { useAnalytics } from "hooks/services/Analytics"; import { useCurrentUser } from "packages/cloud/services/auth/AuthService"; +import { useCurrentWorkspaceId } from "services/workspaces/WorkspacesService"; export const useIntercom = (): IntercomContextValues => { const intercomContextValues = useIntercomProvider(); const user = useCurrentUser(); - const { analyticsContext } = useAnalytics(); + const workspaceId = useCurrentWorkspaceId(); useEffect(() => { intercomContextValues.boot({ @@ -18,7 +18,7 @@ export const useIntercom = (): IntercomContextValues => { userHash: user.intercomHash, customAttributes: { - workspace_id: analyticsContext.workspaceId, + workspace_id: workspaceId, }, }); @@ -29,11 +29,11 @@ export const useIntercom = (): IntercomContextValues => { useEffect(() => { intercomContextValues.update({ customAttributes: { - workspace_id: analyticsContext.workspace_id, + workspace_id: workspaceId, }, }); // eslint-disable-next-line react-hooks/exhaustive-deps - }, [analyticsContext.workspace_id]); + }, [workspaceId]); return intercomContextValues; }; diff --git a/airbyte-webapp/src/packages/cloud/services/thirdParty/launchdarkly/LDExperimentService.tsx b/airbyte-webapp/src/packages/cloud/services/thirdParty/launchdarkly/LDExperimentService.tsx index 0e507781c55e4..9729ecd8dc4f0 100644 --- a/airbyte-webapp/src/packages/cloud/services/thirdParty/launchdarkly/LDExperimentService.tsx +++ b/airbyte-webapp/src/packages/cloud/services/thirdParty/launchdarkly/LDExperimentService.tsx @@ -8,7 +8,7 @@ import { LoadingPage } from "components"; import { useConfig } from "config"; import { useI18nContext } from "core/i18n"; -import { useAnalytics } from "hooks/services/Analytics"; +import { useAnalyticsService } from "hooks/services/Analytics"; import { ExperimentProvider, ExperimentService } from "hooks/services/Experiment"; import type { Experiments } from "hooks/services/Experiment/experiments"; import { FeatureSet, useFeatureService } from "hooks/services/Feature"; @@ -46,7 +46,7 @@ const LDInitializationWrapper: React.FC(); const [state, setState] = useState("initializing"); const { user } = useAuthService(); - const { addContextProps: addAnalyticsContext } = useAnalytics(); + const analyticsService = useAnalyticsService(); const { locale } = useIntl(); const { setMessageOverwrite } = useI18nContext(); @@ -93,7 +93,7 @@ const LDInitializationWrapper: React.FC { const onFeatureFlagsChanged = () => { // Update analytics context whenever a flag changes - addAnalyticsContext({ experiments: JSON.stringify(ldClient.current?.allFlags()) }); + analyticsService.setContext({ experiments: JSON.stringify(ldClient.current?.allFlags()) }); // Check for overwritten i18n messages updateI18nMessages(); }; diff --git a/airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.tsx b/airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.tsx index 3b715057d52ab..ad203723cf84d 100644 --- a/airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.tsx +++ b/airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.tsx @@ -10,6 +10,7 @@ import { CloudRoutes } from "packages/cloud/cloudRoutes"; import { useExperimentSpeedyConnection } from "packages/cloud/components/experiments/SpeedyConnection/hooks/useExperimentSpeedyConnection"; import { SpeedyConnectionBanner } from "packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner"; import { CreditStatus } from "packages/cloud/lib/domain/cloudWorkspaces/types"; +import { useIntercom } from "packages/cloud/services/thirdParty/intercom"; import { useGetCloudWorkspace } from "packages/cloud/services/workspaces/CloudWorkspacesService"; import SideBar from "packages/cloud/views/layout/SideBar"; import { useCurrentWorkspace } from "services/workspaces/WorkspacesService"; @@ -21,6 +22,7 @@ import styles from "./MainView.module.scss"; const MainView: React.FC> = (props) => { const { formatMessage } = useIntl(); + useIntercom(); const workspace = useCurrentWorkspace(); const cloudWorkspace = useGetCloudWorkspace(workspace.workspaceId); const showCreditsBanner = diff --git a/airbyte-webapp/src/packages/cloud/views/workspaces/WorkspacesPage/WorkspacesPage.tsx b/airbyte-webapp/src/packages/cloud/views/workspaces/WorkspacesPage/WorkspacesPage.tsx index 5c887d39d36ac..4cf812c449815 100644 --- a/airbyte-webapp/src/packages/cloud/views/workspaces/WorkspacesPage/WorkspacesPage.tsx +++ b/airbyte-webapp/src/packages/cloud/views/workspaces/WorkspacesPage/WorkspacesPage.tsx @@ -5,12 +5,14 @@ import { Heading } from "components/ui/Heading"; import { Text } from "components/ui/Text"; import { useTrackPage, PageTrackingCodes } from "hooks/services/Analytics"; +import { useIntercom } from "packages/cloud/services/thirdParty/intercom"; import WorkspacesList from "./components/WorkspacesList"; import styles from "./WorkspacesPage.module.scss"; const WorkspacesPage: React.FC = () => { useTrackPage(PageTrackingCodes.WORKSPACES); + useIntercom(); return (
diff --git a/airbyte-webapp/src/views/common/AnalyticsProvider.tsx b/airbyte-webapp/src/views/common/AnalyticsProvider.tsx index fe8a30b37b27a..c32ff53e89163 100644 --- a/airbyte-webapp/src/views/common/AnalyticsProvider.tsx +++ b/airbyte-webapp/src/views/common/AnalyticsProvider.tsx @@ -4,11 +4,9 @@ import { useConfig } from "config"; import AnalyticsServiceProvider from "hooks/services/Analytics/useAnalyticsService"; import useSegment from "hooks/useSegment"; -const AnalyticsProvider: React.FC> = ({ children }) => { +export const AnalyticsProvider: React.FC> = ({ children }) => { const config = useConfig(); useSegment(config.segment.enabled ? config.segment.token : ""); - return {children}; + return {children}; }; - -export { AnalyticsProvider };