Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: provide worspaceId to AnalyticsInitializer #7642

Merged
merged 12 commits into from
Nov 15, 2021
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>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think something like additionalContext suits better.

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