Skip to content

Commit 92c89ec

Browse files
Tim Roesakashkulk
authored andcommitted
🪟 🐛 Remove reinitialization of AnalyticsService (#19304)
* Remvoe reinitialization of AnalyticsService * Change to ref * Make useCurrentWorkspaceId work globally * Fix workspace id
1 parent 64e0dcd commit 92c89ec

File tree

11 files changed

+59
-80
lines changed

11 files changed

+59
-80
lines changed

airbyte-webapp/.storybook/withProvider.tsx

Lines changed: 7 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,14 @@ import { FeatureService } from "../src/hooks/services/Feature";
1212
import { ConfigServiceProvider, defaultConfig } from "../src/config";
1313
import { DocumentationPanelProvider } from "../src/views/Connector/ConnectorDocumentationLayout/DocumentationPanelContext";
1414
import { ServicesProvider } from "../src/core/servicesProvider";
15-
import { analyticsServiceContext, AnalyticsServiceProviderValue } from "../src/hooks/services/Analytics";
15+
import { analyticsServiceContext } from "../src/hooks/services/Analytics";
16+
import type { AnalyticsService } from "../src/core/analytics";
1617

17-
const AnalyticsContextMock: AnalyticsServiceProviderValue = {
18-
analyticsContext: {},
19-
setContext: () => {},
20-
addContextProps: () => {},
21-
removeContextProps: () => {},
22-
service: {
18+
const analyticsContextMock: AnalyticsService = {
2319
track: () => {},
24-
},
25-
} as unknown as AnalyticsServiceProviderValue;
20+
setContext: () => {},
21+
removeFromContext: () => {},
22+
} as unknown as AnalyticsService;
2623

2724
const queryClient = new QueryClient({
2825
defaultOptions: {
@@ -35,7 +32,7 @@ const queryClient = new QueryClient({
3532

3633
export const withProviders = (getStory) => (
3734
<React.Suspense fallback={null}>
38-
<analyticsServiceContext.Provider value={AnalyticsContextMock}>
35+
<analyticsServiceContext.Provider value={analyticsContextMock}>
3936
<QueryClientProvider client={queryClient}>
4037
<ServicesProvider>
4138
<MemoryRouter>

airbyte-webapp/src/core/analytics/AnalyticsService.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,13 @@ describe("AnalyticsService", () => {
2626
});
2727

2828
it("should send events to segment", () => {
29-
const service = new AnalyticsService({});
29+
const service = new AnalyticsService();
3030
service.track(Namespace.CONNECTION, Action.CREATE, {});
3131
expect(window.analytics.track).toHaveBeenCalledWith("Airbyte.UI.Connection.Create", expect.anything());
3232
});
3333

3434
it("should send version and environment for prod", () => {
35-
const service = new AnalyticsService({}, "0.42.13");
35+
const service = new AnalyticsService("0.42.13");
3636
service.track(Namespace.CONNECTION, Action.CREATE, {});
3737
expect(window.analytics.track).toHaveBeenCalledWith(
3838
expect.anything(),
@@ -41,7 +41,7 @@ describe("AnalyticsService", () => {
4141
});
4242

4343
it("should send version and environment for dev", () => {
44-
const service = new AnalyticsService({}, "dev");
44+
const service = new AnalyticsService("dev");
4545
service.track(Namespace.CONNECTION, Action.CREATE, {});
4646
expect(window.analytics.track).toHaveBeenCalledWith(
4747
expect.anything(),
@@ -50,7 +50,7 @@ describe("AnalyticsService", () => {
5050
});
5151

5252
it("should pass parameters to segment event", () => {
53-
const service = new AnalyticsService({});
53+
const service = new AnalyticsService();
5454
service.track(Namespace.CONNECTION, Action.CREATE, { actionDescription: "Created new connection" });
5555
expect(window.analytics.track).toHaveBeenCalledWith(
5656
expect.anything(),
@@ -59,7 +59,8 @@ describe("AnalyticsService", () => {
5959
});
6060

6161
it("should pass context parameters to segment event", () => {
62-
const service = new AnalyticsService({ context: 42 });
62+
const service = new AnalyticsService();
63+
service.setContext({ context: 42 });
6364
service.track(Namespace.CONNECTION, Action.CREATE, { actionDescription: "Created new connection" });
6465
expect(window.analytics.track).toHaveBeenCalledWith(
6566
expect.anything(),

airbyte-webapp/src/core/analytics/AnalyticsService.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,25 @@
11
import { Action, EventParams, Namespace } from "./types";
22

3+
type Context = Record<string, unknown>;
4+
35
export class AnalyticsService {
4-
constructor(private context: Record<string, unknown>, private version?: string) {}
6+
private context: Context = {};
7+
8+
constructor(private version?: string) {}
59

610
private getSegmentAnalytics = (): SegmentAnalytics.AnalyticsJS | undefined => window.analytics;
711

12+
public setContext(context: Context) {
13+
this.context = {
14+
...this.context,
15+
...context,
16+
};
17+
}
18+
19+
public removeFromContext(...keys: string[]) {
20+
keys.forEach((key) => delete this.context[key]);
21+
}
22+
823
alias = (newId: string): void => this.getSegmentAnalytics()?.alias?.(newId);
924

1025
page = (name: string): void => this.getSegmentAnalytics()?.page?.(name, { ...this.context });

airbyte-webapp/src/hooks/services/Analytics/__mocks__/useAnalyticsService.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,7 @@ export const useAnalyticsService = (): AnalyticsService => {
88
track: jest.fn(),
99
identify: jest.fn(),
1010
group: jest.fn(),
11+
setContext: jest.fn(),
12+
removeFromContext: jest.fn(),
1113
} as unknown as AnalyticsService;
1214
};

airbyte-webapp/src/hooks/services/Analytics/useAnalyticsService.tsx

Lines changed: 13 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,26 @@
1-
import React, { useContext, useEffect, useMemo } from "react";
2-
import { useMap } from "react-use";
1+
import React, { useContext, useEffect, useRef } from "react";
32

3+
import { useConfig } from "config";
44
import { AnalyticsService } from "core/analytics/AnalyticsService";
55

66
type AnalyticsContext = Record<string, unknown>;
77

8-
export interface AnalyticsServiceProviderValue {
9-
analyticsContext: AnalyticsContext;
10-
setContext: (ctx: AnalyticsContext) => void;
11-
addContextProps: (props: AnalyticsContext) => void;
12-
removeContextProps: (props: string[]) => void;
13-
service: AnalyticsService;
14-
}
15-
16-
export const analyticsServiceContext = React.createContext<AnalyticsServiceProviderValue | null>(null);
17-
18-
const AnalyticsServiceProvider = ({
19-
children,
20-
version,
21-
initialContext = {},
22-
}: {
23-
children: React.ReactNode;
24-
version?: string;
25-
initialContext?: AnalyticsContext;
26-
}) => {
27-
const [analyticsContext, { set, setAll, remove }] = useMap(initialContext);
28-
29-
const analyticsService: AnalyticsService = useMemo(
30-
() => new AnalyticsService(analyticsContext, version),
31-
[version, analyticsContext]
32-
);
8+
export const analyticsServiceContext = React.createContext<AnalyticsService | null>(null);
339

34-
const handleAddContextProps = (props: AnalyticsContext) => {
35-
Object.entries(props).forEach((value) => set(...value));
36-
};
10+
const AnalyticsServiceProvider: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
11+
const { version } = useConfig();
12+
const analyticsService = useRef<AnalyticsService>();
3713

38-
const handleRemoveContextProps = (props: string[]) => props.forEach(remove);
14+
if (!analyticsService.current) {
15+
analyticsService.current = new AnalyticsService(version);
16+
}
3917

4018
return (
41-
<analyticsServiceContext.Provider
42-
value={{
43-
analyticsContext,
44-
setContext: setAll,
45-
addContextProps: handleAddContextProps,
46-
removeContextProps: handleRemoveContextProps,
47-
service: analyticsService,
48-
}}
49-
>
50-
{children}
51-
</analyticsServiceContext.Provider>
19+
<analyticsServiceContext.Provider value={analyticsService.current}>{children}</analyticsServiceContext.Provider>
5220
);
5321
};
5422

5523
export const useAnalyticsService = (): AnalyticsService => {
56-
return useAnalytics().service;
57-
};
58-
59-
export const useAnalytics = (): AnalyticsServiceProviderValue => {
6024
const analyticsContext = useContext(analyticsServiceContext);
6125

6226
if (!analyticsContext) {
@@ -86,15 +50,15 @@ export const useTrackPage = (page: string): void => {
8650
};
8751

8852
export const useAnalyticsRegisterValues = (props?: AnalyticsContext | null): void => {
89-
const { addContextProps, removeContextProps } = useAnalytics();
53+
const service = useAnalyticsService();
9054

9155
useEffect(() => {
9256
if (!props) {
9357
return;
9458
}
9559

96-
addContextProps(props);
97-
return () => removeContextProps(Object.keys(props));
60+
service.setContext(props);
61+
return () => service.removeFromContext(...Object.keys(props));
9862

9963
// eslint-disable-next-line react-hooks/exhaustive-deps
10064
}, [props]);

airbyte-webapp/src/packages/cloud/cloudRoutes.tsx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ import { OnboardingServiceProvider } from "hooks/services/Onboarding";
1313
import { useQuery } from "hooks/useQuery";
1414
import { useExperimentSpeedyConnection } from "packages/cloud/components/experiments/SpeedyConnection/hooks/useExperimentSpeedyConnection";
1515
import { useAuthService } from "packages/cloud/services/auth/AuthService";
16-
import { useIntercom } from "packages/cloud/services/thirdParty/intercom/useIntercom";
1716
import { Auth } from "packages/cloud/views/auth";
1817
import { CreditsPage } from "packages/cloud/views/credits";
1918
import MainView from "packages/cloud/views/layout/MainView";
@@ -114,7 +113,6 @@ const MainRoutes: React.FC = () => {
114113

115114
const MainViewRoutes = () => {
116115
useApiHealthPoll();
117-
useIntercom();
118116
const query = useQuery<{ from: string }>();
119117

120118
return (
Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { useEffect } from "react";
22
import { useIntercom as useIntercomProvider, IntercomContextValues } from "react-use-intercom";
33

4-
import { useAnalytics } from "hooks/services/Analytics";
54
import { useCurrentUser } from "packages/cloud/services/auth/AuthService";
5+
import { useCurrentWorkspaceId } from "services/workspaces/WorkspacesService";
66

77
export const useIntercom = (): IntercomContextValues => {
88
const intercomContextValues = useIntercomProvider();
99

1010
const user = useCurrentUser();
11-
const { analyticsContext } = useAnalytics();
11+
const workspaceId = useCurrentWorkspaceId();
1212

1313
useEffect(() => {
1414
intercomContextValues.boot({
@@ -18,7 +18,7 @@ export const useIntercom = (): IntercomContextValues => {
1818
userHash: user.intercomHash,
1919

2020
customAttributes: {
21-
workspace_id: analyticsContext.workspaceId,
21+
workspace_id: workspaceId,
2222
},
2323
});
2424

@@ -29,11 +29,11 @@ export const useIntercom = (): IntercomContextValues => {
2929
useEffect(() => {
3030
intercomContextValues.update({
3131
customAttributes: {
32-
workspace_id: analyticsContext.workspace_id,
32+
workspace_id: workspaceId,
3333
},
3434
});
3535
// eslint-disable-next-line react-hooks/exhaustive-deps
36-
}, [analyticsContext.workspace_id]);
36+
}, [workspaceId]);
3737

3838
return intercomContextValues;
3939
};

airbyte-webapp/src/packages/cloud/services/thirdParty/launchdarkly/LDExperimentService.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { LoadingPage } from "components";
88

99
import { useConfig } from "config";
1010
import { useI18nContext } from "core/i18n";
11-
import { useAnalytics } from "hooks/services/Analytics";
11+
import { useAnalyticsService } from "hooks/services/Analytics";
1212
import { ExperimentProvider, ExperimentService } from "hooks/services/Experiment";
1313
import type { Experiments } from "hooks/services/Experiment/experiments";
1414
import { FeatureSet, useFeatureService } from "hooks/services/Feature";
@@ -46,7 +46,7 @@ const LDInitializationWrapper: React.FC<React.PropsWithChildren<{ apiKey: string
4646
const ldClient = useRef<LDClient.LDClient>();
4747
const [state, setState] = useState<LDInitState>("initializing");
4848
const { user } = useAuthService();
49-
const { addContextProps: addAnalyticsContext } = useAnalytics();
49+
const analyticsService = useAnalyticsService();
5050
const { locale } = useIntl();
5151
const { setMessageOverwrite } = useI18nContext();
5252

@@ -93,7 +93,7 @@ const LDInitializationWrapper: React.FC<React.PropsWithChildren<{ apiKey: string
9393
// The LaunchDarkly promise resolved before the timeout, so we're good to use LD.
9494
setState("initialized");
9595
// Make sure enabled experiments are added to each analytics event
96-
addAnalyticsContext({ experiments: JSON.stringify(ldClient.current?.allFlags()) });
96+
analyticsService.setContext({ experiments: JSON.stringify(ldClient.current?.allFlags()) });
9797
// Check for overwritten i18n messages
9898
updateI18nMessages();
9999
updateFeatureOverwrites(ldClient.current?.variation(FEATURE_FLAG_EXPERIMENT, ""));
@@ -118,7 +118,7 @@ const LDInitializationWrapper: React.FC<React.PropsWithChildren<{ apiKey: string
118118
useEffectOnce(() => {
119119
const onFeatureFlagsChanged = () => {
120120
// Update analytics context whenever a flag changes
121-
addAnalyticsContext({ experiments: JSON.stringify(ldClient.current?.allFlags()) });
121+
analyticsService.setContext({ experiments: JSON.stringify(ldClient.current?.allFlags()) });
122122
// Check for overwritten i18n messages
123123
updateI18nMessages();
124124
};

airbyte-webapp/src/packages/cloud/views/layout/MainView/MainView.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { CloudRoutes } from "packages/cloud/cloudRoutes";
1010
import { useExperimentSpeedyConnection } from "packages/cloud/components/experiments/SpeedyConnection/hooks/useExperimentSpeedyConnection";
1111
import { SpeedyConnectionBanner } from "packages/cloud/components/experiments/SpeedyConnection/SpeedyConnectionBanner";
1212
import { CreditStatus } from "packages/cloud/lib/domain/cloudWorkspaces/types";
13+
import { useIntercom } from "packages/cloud/services/thirdParty/intercom";
1314
import { useGetCloudWorkspace } from "packages/cloud/services/workspaces/CloudWorkspacesService";
1415
import SideBar from "packages/cloud/views/layout/SideBar";
1516
import { useCurrentWorkspace } from "services/workspaces/WorkspacesService";
@@ -21,6 +22,7 @@ import styles from "./MainView.module.scss";
2122

2223
const MainView: React.FC<React.PropsWithChildren<unknown>> = (props) => {
2324
const { formatMessage } = useIntl();
25+
useIntercom();
2426
const workspace = useCurrentWorkspace();
2527
const cloudWorkspace = useGetCloudWorkspace(workspace.workspaceId);
2628
const showCreditsBanner =

airbyte-webapp/src/packages/cloud/views/workspaces/WorkspacesPage/WorkspacesPage.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ import { Heading } from "components/ui/Heading";
55
import { Text } from "components/ui/Text";
66

77
import { useTrackPage, PageTrackingCodes } from "hooks/services/Analytics";
8+
import { useIntercom } from "packages/cloud/services/thirdParty/intercom";
89

910
import WorkspacesList from "./components/WorkspacesList";
1011
import styles from "./WorkspacesPage.module.scss";
1112

1213
const WorkspacesPage: React.FC = () => {
1314
useTrackPage(PageTrackingCodes.WORKSPACES);
15+
useIntercom();
1416

1517
return (
1618
<div className={styles.container}>

airbyte-webapp/src/views/common/AnalyticsProvider.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,9 @@ import { useConfig } from "config";
44
import AnalyticsServiceProvider from "hooks/services/Analytics/useAnalyticsService";
55
import useSegment from "hooks/useSegment";
66

7-
const AnalyticsProvider: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
7+
export const AnalyticsProvider: React.FC<React.PropsWithChildren<unknown>> = ({ children }) => {
88
const config = useConfig();
99
useSegment(config.segment.enabled ? config.segment.token : "");
1010

11-
return <AnalyticsServiceProvider version={config.version}>{children}</AnalyticsServiceProvider>;
11+
return <AnalyticsServiceProvider>{children}</AnalyticsServiceProvider>;
1212
};
13-
14-
export { AnalyticsProvider };

0 commit comments

Comments
 (0)