Skip to content

Commit

Permalink
fix: provide worspaceId to AnalyticsInitializer (#7642)
Browse files Browse the repository at this point in the history
* fix: provide worspaceId to AnalyticsInitializer

* Refactor Analytics for more independent approach

* Speed up initial load for cloud webapp

Co-authored-by: Artem Astapenko <jamakase54@gmail.com>
  • Loading branch information
isalikov and jamakase authored Nov 15, 2021
1 parent 495202e commit 35e6795
Show file tree
Hide file tree
Showing 39 changed files with 390 additions and 302 deletions.
34 changes: 12 additions & 22 deletions airbyte-webapp/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,8 @@ import { Routing } from "./pages/routes";
import LoadingPage from "./components/LoadingPage";
import ApiErrorBoundary from "./components/ApiErrorBoundary";
import NotificationService from "hooks/services/Notification";
import { AnalyticsInitializer } from "views/common/AnalyticsInitializer";
import {
useCurrentWorkspace,
usePickFirstWorkspace,
} from "hooks/services/useWorkspace";
import { AnalyticsProvider } from "views/common/AnalyticsProvider";
import { usePickFirstWorkspace } from "hooks/services/useWorkspace";
import { Feature, FeatureItem, FeatureService } from "hooks/services/Feature";
import { OnboardingServiceProvider } from "hooks/services/Onboarding";
import { ServicesProvider } from "core/servicesProvider";
Expand All @@ -29,12 +26,6 @@ import {
ValueProvider,
} from "./config";

function useCustomerIdProvider() {
const workspace = useCurrentWorkspace();

return workspace.customerId;
}

const Features: Feature[] = [
{
id: FeatureItem.AllowUploadCustomImage,
Expand Down Expand Up @@ -75,7 +66,6 @@ const configProviders: ValueProvider<Config> = [

const services = {
currentWorkspaceProvider: usePickFirstWorkspace,
useCustomerIdProvider: useCustomerIdProvider,
};

const AppServices: React.FC = ({ children }) => (
Expand All @@ -100,19 +90,19 @@ const App: React.FC = () => {
defaultConfig={defaultConfig}
providers={configProviders}
>
<ApiErrorBoundary>
<FeatureService features={Features}>
<NotificationService>
<AppServices>
<AnalyticsInitializer>
<AnalyticsProvider>
<ApiErrorBoundary>
<FeatureService features={Features}>
<NotificationService>
<AppServices>
<OnboardingServiceProvider>
<Routing />
</OnboardingServiceProvider>
</AnalyticsInitializer>
</AppServices>
</NotificationService>
</FeatureService>
</ApiErrorBoundary>
</AppServices>
</NotificationService>
</FeatureService>
</ApiErrorBoundary>
</AnalyticsProvider>
</ConfigServiceProvider>
</Suspense>
</StoreProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { useDiscoverSchema } from "hooks/services/useSchemaHook";
import SourceDefinitionResource from "core/resources/SourceDefinition";
import DestinationDefinitionResource from "core/resources/DestinationDefinition";
import { IDataItem } from "components/base/DropDown/components/Option";
import { useAnalytics } from "hooks/useAnalytics";
import { useAnalyticsService } from "hooks/services/Analytics/useAnalyticsService";

const SkipButton = styled.div`
margin-top: 6px;
Expand Down Expand Up @@ -51,7 +51,7 @@ const CreateConnectionContent: React.FC<IProps> = ({
noTitles,
}) => {
const { createConnection } = useConnection();
const analyticsService = useAnalytics();
const analyticsService = useAnalyticsService();

const sourceDefinition = useResource(SourceDefinitionResource.detailShape(), {
sourceDefinitionId: source.sourceDefinitionId,
Expand Down
4 changes: 2 additions & 2 deletions airbyte-webapp/src/components/EntityTable/hooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import FrequencyConfig from "config/FrequencyConfig.json";
import ConnectionResource, { Connection } from "core/resources/Connection";
import useConnection from "hooks/services/useConnectionHook";
import { Status } from "./types";
import { useAnalytics } from "hooks/useAnalytics";
import { useAnalyticsService } from "hooks/services/Analytics/useAnalyticsService";

const useSyncActions = (): {
changeStatus: (connection: Connection) => Promise<void>;
syncManualConnection: (connection: Connection) => Promise<void>;
} => {
const { updateConnection } = useConnection();
const SyncConnection = useFetcher(ConnectionResource.syncShape());
const analyticsService = useAnalytics();
const analyticsService = useAnalyticsService();

const changeStatus = async (connection: Connection) => {
await updateConnection({
Expand Down
7 changes: 5 additions & 2 deletions airbyte-webapp/src/core/analytics/AnalyticsService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { SegmentAnalytics } from "./types";

export class AnalyticsService {
constructor(private userId?: string, private version?: string) {}
constructor(
private context: Record<string, unknown>,
private version?: string
) {}

private getSegmentAnalytics = (): SegmentAnalytics | undefined =>
window.analytics;
Expand All @@ -14,8 +17,8 @@ export class AnalyticsService {

track = (name: string, properties: Record<string, unknown>): void =>
this.getSegmentAnalytics()?.track?.(name, {
user_id: this.userId,
...properties,
...this.context,
airbyte_version: this.version,
environment: this.version === "dev" ? "dev" : "prod",
});
Expand Down
19 changes: 19 additions & 0 deletions airbyte-webapp/src/hooks/services/Analytics/TrackPageAnalytics.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, { useEffect } from "react";

import useRouter from "hooks/useRouter";

import { useAnalyticsService } from "./useAnalyticsService";
import { getPageName } from "./pageNameUtils";

export const TrackPageAnalytics: React.FC = () => {
const { pathname } = useRouter();
const analyticsService = useAnalyticsService();
useEffect(() => {
const pageName = getPageName(pathname);
if (pageName) {
analyticsService.page(pageName);
}
}, [analyticsService, pathname]);

return null;
};
2 changes: 2 additions & 0 deletions airbyte-webapp/src/hooks/services/Analytics/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./TrackPageAnalytics";
export * from "./useAnalyticsService";
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
import React, { useEffect } from "react";
import { Routes } from "pages/routes";

import useRouter from "hooks/useRouter";
import { useAnalytics } from "hooks/useAnalytics";
import { Routes } from "./routes";

const getPageName = (pathname: string) => {
const getPageName = (pathname: string): string => {
const itemSourcePageRegex = new RegExp(`${Routes.Source}/.*`);
const itemDestinationPageRegex = new RegExp(`${Routes.Destination}/.*`);
const itemSourceToDestinationPageRegex = new RegExp(
Expand Down Expand Up @@ -60,15 +56,4 @@ const getPageName = (pathname: string) => {
return "";
};

export const WithPageAnalytics: React.FC = () => {
const { pathname } = useRouter();
const analyticsService = useAnalytics();
useEffect(() => {
const pageName = getPageName(pathname);
if (pageName) {
analyticsService.page(pageName);
}
}, [analyticsService, pathname]);

return null;
};
export { getPageName };
101 changes: 101 additions & 0 deletions airbyte-webapp/src/hooks/services/Analytics/useAnalyticsService.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import React, { useContext, useEffect, useMemo } from "react";
import { useMap } from "react-use";

import { AnalyticsService } from "core/analytics/AnalyticsService";

type AnalyticsContext = Record<string, unknown>;

export type AnalyticsServiceProviderValue = {
analyticsContext: AnalyticsContext;
setContext: (ctx: AnalyticsContext) => void;
addContextProps: (props: AnalyticsContext) => void;
removeContextProps: (props: string[]) => void;
service: AnalyticsService;
};

const analyticsServiceContext = React.createContext<AnalyticsServiceProviderValue | null>(
null
);

function 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]
);

const handleAddContextProps = (props: AnalyticsContext) => {
Object.entries(props).forEach((value) => set(...value));
};

const handleRemoveContextProps = (props: string[]) => props.forEach(remove);

return (
<analyticsServiceContext.Provider
value={{
analyticsContext,
setContext: setAll,
addContextProps: handleAddContextProps,
removeContextProps: handleRemoveContextProps,
service: analyticsService,
}}
>
{children}
</analyticsServiceContext.Provider>
);
}

export const useAnalyticsService = (): AnalyticsService => {
const analyticsService = useAnalytics();

return analyticsService.service;
};

export const useAnalytics = (): AnalyticsServiceProviderValue => {
const analyticsContext = useContext(analyticsServiceContext);

if (!analyticsContext) {
throw new Error(
"analyticsContext must be used within a AnalyticsServiceProvider."
);
}

return analyticsContext;
};

export const useAnalyticsIdentifyUser = (userId?: string): void => {
const analyticsService = useAnalyticsService();

useEffect(() => {
if (userId) {
analyticsService.identify(userId);
}
}, [userId]);
};

export const useAnalyticsRegisterValues = (
props?: AnalyticsContext | null
): void => {
const { addContextProps, removeContextProps } = useAnalytics();

useEffect(() => {
if (props) {
addContextProps(props);

return () => removeContextProps(Object.keys(props));
}

return;
}, [props]);
};

export default React.memo(AnalyticsServiceProvider);
6 changes: 4 additions & 2 deletions airbyte-webapp/src/hooks/services/Notification/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import NotificationService from "./NotificationService";
import NotificationService, {
useNotificationService,
} from "./NotificationService";

export default NotificationService;
export { NotificationService };
export { NotificationService, useNotificationService };
4 changes: 2 additions & 2 deletions airbyte-webapp/src/hooks/services/useConnectionHook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { Routes } from "pages/routes";
import { Destination } from "core/resources/Destination";
import useWorkspace from "./useWorkspace";
import { Operation } from "core/domain/connection/operation";
import { useAnalytics } from "hooks/useAnalytics";
import { useAnalyticsService } from "hooks/services/Analytics/useAnalyticsService";
import useRouter from "hooks/useRouter";
import { useGetService } from "core/servicesProvider";
import { RequestMiddleware } from "core/request/RequestMiddleware";
Expand Down Expand Up @@ -108,7 +108,7 @@ const useConnection = (): {
} => {
const { push } = useRouter();
const { workspace } = useWorkspace();
const analyticsService = useAnalytics();
const analyticsService = useAnalyticsService();

const createConnectionResource = useFetcher(ConnectionResource.createShape());
const updateConnectionResource = useFetcher(ConnectionResource.updateShape());
Expand Down
4 changes: 2 additions & 2 deletions airbyte-webapp/src/hooks/services/useDestinationHook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import DestinationDefinitionSpecificationResource from "core/resources/Destinati
import SchedulerResource, { Scheduler } from "core/resources/Scheduler";
import { ConnectionConfiguration } from "core/domain/connection";
import useWorkspace from "./useWorkspace";
import { useAnalytics } from "hooks/useAnalytics";
import { useAnalyticsService } from "hooks/services/Analytics/useAnalyticsService";
import { DestinationDefinitionSpecification } from "core/domain/connector";

type ValuesProps = {
Expand Down Expand Up @@ -102,7 +102,7 @@ type DestinationService = {
const useDestination = (): DestinationService => {
const { push } = useRouter();
const { workspace } = useWorkspace();
const analyticsService = useAnalytics();
const analyticsService = useAnalyticsService();
const createDestinationsImplementation = useFetcher(
DestinationResource.createShape()
);
Expand Down
4 changes: 2 additions & 2 deletions airbyte-webapp/src/hooks/services/useRequestConnector.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useAnalytics } from "hooks/useAnalytics";
import { useAnalyticsService } from "hooks/services/Analytics/useAnalyticsService";

type Values = {
connectorType: string;
Expand All @@ -10,7 +10,7 @@ type Values = {
const useRequestConnector = (): {
requestConnector: (conn: Values) => void;
} => {
const analyticsService = useAnalytics();
const analyticsService = useAnalyticsService();

const requestConnector = (values: Values) => {
analyticsService.track("Request a Connector", {
Expand Down
4 changes: 2 additions & 2 deletions airbyte-webapp/src/hooks/services/useSourceHook.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import { ConnectionConfiguration } from "core/domain/connection";
import useWorkspace from "./useWorkspace";

import useRouter from "hooks/useRouter";
import { useAnalytics } from "hooks/useAnalytics";
import { useAnalyticsService } from "hooks/services/Analytics/useAnalyticsService";
import { SourceDefinitionSpecification } from "core/domain/connector";

type ValuesProps = {
Expand Down Expand Up @@ -73,7 +73,7 @@ const useSource = (): SourceService => {
const { push } = useRouter();
const { workspace } = useWorkspace();
const createSourcesImplementation = useFetcher(SourceResource.createShape());
const analyticsService = useAnalytics();
const analyticsService = useAnalyticsService();

const sourceCheckConnectionShape = useFetcher(
SchedulerResource.sourceCheckConnectionShape()
Expand Down
4 changes: 2 additions & 2 deletions airbyte-webapp/src/hooks/services/useWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import NotificationsResource, {
Notifications,
} from "core/resources/Notifications";
import { useGetService } from "core/servicesProvider";
import { useAnalytics } from "../useAnalytics";
import { useAnalyticsService } from "./Analytics/useAnalyticsService";
import { Source } from "core/resources/Source";
import { Destination } from "core/resources/Destination";

Expand Down Expand Up @@ -60,7 +60,7 @@ const useWorkspace = (): {
const tryWebhookUrl = useFetcher(NotificationsResource.tryShape());
const workspace = useCurrentWorkspace();

const analyticsService = useAnalytics();
const analyticsService = useAnalyticsService();

const finishOnboarding = async (skipStep?: string) => {
if (skipStep) {
Expand Down
Loading

0 comments on commit 35e6795

Please sign in to comment.