From 7e79cacf77a54c3d58cc740ce59eccd91773dc97 Mon Sep 17 00:00:00 2001 From: Edmundo Ruiz Ghanem <168664+edmundito@users.noreply.github.com> Date: Tue, 3 May 2022 14:30:27 -0400 Subject: [PATCH] Fix new source / destination test connection success / failure tracking events (#12526) * Consolidate track new action analytics calls into hook * Move new source and destination test tracking to where the actual connection is being tested and remove from creation * Use consistent properties across all track new source actions * Make track action hook more generic and support new destination actions Update new destination actions with useTrackAction hook * Use connector_source_definition_id over connector_source_id for track new source actions --- .../src/core/analytics/AnalyticsService.ts | 2 +- .../src/hooks/services/useDestinationHook.tsx | 16 +----- .../src/hooks/services/useSourceHook.tsx | 19 ------- airbyte-webapp/src/hooks/useTrackAction.ts | 26 ++++++++++ .../components/DestinationForm.tsx | 9 ++-- .../components/DestinationStep.tsx | 7 ++- .../OnboardingPage/components/SourceStep.tsx | 9 ++-- .../components/SourceForm.tsx | 9 ++-- .../Connector/ConnectorCard/ConnectorCard.tsx | 51 ++++++++++++------- 9 files changed, 75 insertions(+), 73 deletions(-) create mode 100644 airbyte-webapp/src/hooks/useTrackAction.ts diff --git a/airbyte-webapp/src/core/analytics/AnalyticsService.ts b/airbyte-webapp/src/core/analytics/AnalyticsService.ts index ea0bf2a0af35..125a1887bb89 100644 --- a/airbyte-webapp/src/core/analytics/AnalyticsService.ts +++ b/airbyte-webapp/src/core/analytics/AnalyticsService.ts @@ -11,7 +11,7 @@ export class AnalyticsService { reset = (): void => this.getSegmentAnalytics()?.reset?.(); - track = (name: string, properties: Record): void => + track =

>(name: string, properties: P): void => this.getSegmentAnalytics()?.track?.(name, { ...properties, ...this.context, diff --git a/airbyte-webapp/src/hooks/services/useDestinationHook.tsx b/airbyte-webapp/src/hooks/services/useDestinationHook.tsx index 8969c078bec8..8e01a6216938 100644 --- a/airbyte-webapp/src/hooks/services/useDestinationHook.tsx +++ b/airbyte-webapp/src/hooks/services/useDestinationHook.tsx @@ -64,8 +64,6 @@ const useCreateDestination = () => { const queryClient = useQueryClient(); const workspace = useCurrentWorkspace(); - const analyticsService = useAnalyticsService(); - return useMutation( async (createDestinationPayload: { values: ValuesProps; destinationConnector?: ConnectorProps }) => { const { values, destinationConnector } = createDestinationPayload; @@ -78,23 +76,11 @@ const useCreateDestination = () => { }); }, { - onSuccess: (data, ctx) => { - analyticsService.track("New Destination - Action", { - action: "Tested connector - success", - connector_destination: ctx.destinationConnector?.name, - connector_destination_definition_id: ctx.destinationConnector?.destinationDefinitionId, - }); + onSuccess: (data) => { queryClient.setQueryData(destinationsKeys.lists(), (lst: DestinationList | undefined) => ({ destinations: [data, ...(lst?.destinations ?? [])], })); }, - onError: (_, ctx) => { - analyticsService.track("New Destination - Action", { - action: "Tested connector - failure", - connector_destination: ctx.destinationConnector?.name, - connector_destination_definition_id: ctx.destinationConnector?.destinationDefinitionId, - }); - }, } ); }; diff --git a/airbyte-webapp/src/hooks/services/useSourceHook.tsx b/airbyte-webapp/src/hooks/services/useSourceHook.tsx index 8b51497e0861..cbde18076722 100644 --- a/airbyte-webapp/src/hooks/services/useSourceHook.tsx +++ b/airbyte-webapp/src/hooks/services/useSourceHook.tsx @@ -68,17 +68,9 @@ const useCreateSource = () => { const queryClient = useQueryClient(); const workspace = useCurrentWorkspace(); - const analyticsService = useAnalyticsService(); - return useMutation( async (createSourcePayload: { values: ValuesProps; sourceConnector?: ConnectorProps }) => { const { values, sourceConnector } = createSourcePayload; - analyticsService.track("New Source - Action", { - action: "Test a connector", - connector_source: sourceConnector?.name, - connector_source_id: sourceConnector?.sourceDefinitionId, - }); - try { // Try to crete source const result = await service.create({ @@ -88,19 +80,8 @@ const useCreateSource = () => { connectionConfiguration: values.connectionConfiguration, }); - analyticsService.track("New Source - Action", { - action: "Tested connector - success", - connector_source: sourceConnector?.name, - connector_source_id: sourceConnector?.sourceDefinitionId, - }); - return result; } catch (e) { - analyticsService.track("New Source - Action", { - action: "Tested connector - failure", - connector_source: sourceConnector?.name, - connector_source_id: sourceConnector?.sourceDefinitionId, - }); throw e; } }, diff --git a/airbyte-webapp/src/hooks/useTrackAction.ts b/airbyte-webapp/src/hooks/useTrackAction.ts new file mode 100644 index 000000000000..8fa44dc179fb --- /dev/null +++ b/airbyte-webapp/src/hooks/useTrackAction.ts @@ -0,0 +1,26 @@ +import { useCallback } from "react"; + +import { useAnalyticsService } from "./services/Analytics/useAnalyticsService"; + +export const enum TrackActionType { + NEW_SOURCE = "New Source", + NEW_DESTINATION = "New Destination", +} + +interface TrackActionProperties { + connector_source?: string; + connector_source_definition_id?: string; + connector_destination?: string; + connector_destination_definition_id?: string; +} + +export const useTrackAction = (type: TrackActionType) => { + const analyticsService = useAnalyticsService(); + + return useCallback( + (action: string, properties: TrackActionProperties) => { + analyticsService.track(`${type} - Action`, { action, ...properties }); + }, + [analyticsService, type] + ); +}; diff --git a/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/components/DestinationForm.tsx b/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/components/DestinationForm.tsx index 9d96a426c163..b6626906b375 100644 --- a/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/components/DestinationForm.tsx +++ b/airbyte-webapp/src/pages/DestinationPage/pages/CreateDestinationPage/components/DestinationForm.tsx @@ -4,8 +4,8 @@ import { FormattedMessage } from "react-intl"; import { ConnectionConfiguration } from "core/domain/connection"; import { DestinationDefinition } from "core/domain/connector"; import { LogsRequestError } from "core/request/LogsRequestError"; -import { useAnalyticsService } from "hooks/services/Analytics/useAnalyticsService"; import useRouter from "hooks/useRouter"; +import { TrackActionType, useTrackAction } from "hooks/useTrackAction"; import { useGetDestinationDefinitionSpecificationAsync } from "services/connector/DestinationDefinitionSpecificationService"; import { createFormErrorMessage } from "utils/errorStatusMessage"; import { ConnectorCard } from "views/Connector/ConnectorCard"; @@ -39,7 +39,7 @@ const DestinationForm: React.FC = ({ afterSelectConnector, }) => { const { location } = useRouter(); - const analyticsService = useAnalyticsService(); + const trackNewDestinationAction = useTrackAction(TrackActionType.NEW_DESTINATION); const [destinationDefinitionId, setDestinationDefinitionId] = useState( hasDestinationDefinitionId(location.state) ? location.state.destinationDefinitionId : null @@ -58,9 +58,8 @@ const DestinationForm: React.FC = ({ afterSelectConnector(); } - analyticsService.track("New Destination - Action", { - action: "Select a connector", - connector_destination_definition: connector?.name, + trackNewDestinationAction("Select a connector", { + connector_destination: connector?.name, connector_destination_definition_id: destinationDefinitionId, }); }; diff --git a/airbyte-webapp/src/pages/OnboardingPage/components/DestinationStep.tsx b/airbyte-webapp/src/pages/OnboardingPage/components/DestinationStep.tsx index 571e784df22b..01dab0e89796 100644 --- a/airbyte-webapp/src/pages/OnboardingPage/components/DestinationStep.tsx +++ b/airbyte-webapp/src/pages/OnboardingPage/components/DestinationStep.tsx @@ -3,8 +3,8 @@ import { FormattedMessage } from "react-intl"; import { ConnectionConfiguration } from "core/domain/connection"; import { JobInfo } from "core/domain/job"; -import { useAnalyticsService } from "hooks/services/Analytics/useAnalyticsService"; import { useCreateDestination } from "hooks/services/useDestinationHook"; +import { TrackActionType, useTrackAction } from "hooks/useTrackAction"; import { useDestinationDefinitionList } from "services/connector/DestinationDefinitionService"; import { useGetDestinationDefinitionSpecificationAsync } from "services/connector/DestinationDefinitionSpecificationService"; import { createFormErrorMessage } from "utils/errorStatusMessage"; @@ -31,7 +31,7 @@ const DestinationStep: React.FC = ({ onNextStep, onSuccess }) => { } | null>(null); const { mutateAsync: createDestination } = useCreateDestination(); - const analyticsService = useAnalyticsService(); + const trackNewDestinationAction = useTrackAction(TrackActionType.NEW_DESTINATION); const getDestinationDefinitionById = (id: string) => destinationDefinitions.find((item) => item.destinationDefinitionId === id); @@ -64,8 +64,7 @@ const DestinationStep: React.FC = ({ onNextStep, onSuccess }) => { const onDropDownSelect = (destinationDefinitionId: string) => { const destinationConnector = getDestinationDefinitionById(destinationDefinitionId); - analyticsService.track("New Destination - Action", { - action: "Select a connector", + trackNewDestinationAction("Select a connector", { connector_destination: destinationConnector?.name, connector_destination_definition_id: destinationConnector?.destinationDefinitionId, }); diff --git a/airbyte-webapp/src/pages/OnboardingPage/components/SourceStep.tsx b/airbyte-webapp/src/pages/OnboardingPage/components/SourceStep.tsx index bbe0b95f619c..30873022fd77 100644 --- a/airbyte-webapp/src/pages/OnboardingPage/components/SourceStep.tsx +++ b/airbyte-webapp/src/pages/OnboardingPage/components/SourceStep.tsx @@ -4,8 +4,8 @@ import { FormattedMessage } from "react-intl"; import { ConnectionConfiguration } from "core/domain/connection"; import { JobInfo } from "core/domain/job"; import { LogsRequestError } from "core/request/LogsRequestError"; -import { useAnalyticsService } from "hooks/services/Analytics/useAnalyticsService"; import { useCreateSource } from "hooks/services/useSourceHook"; +import { TrackActionType, useTrackAction } from "hooks/useTrackAction"; import { useSourceDefinitionList } from "services/connector/SourceDefinitionService"; import { useGetSourceDefinitionSpecificationAsync } from "services/connector/SourceDefinitionSpecificationService"; import { createFormErrorMessage } from "utils/errorStatusMessage"; @@ -31,7 +31,7 @@ const SourceStep: React.FC = ({ onNextStep, onSuccess }) => { const { mutateAsync: createSource } = useCreateSource(); - const analyticsService = useAnalyticsService(); + const trackNewSourceAction = useTrackAction(TrackActionType.NEW_SOURCE); const getSourceDefinitionById = (id: string) => sourceDefinitions.find((item) => item.sourceDefinitionId === id); @@ -64,10 +64,9 @@ const SourceStep: React.FC = ({ onNextStep, onSuccess }) => { const onServiceSelect = (sourceId: string) => { const sourceDefinition = getSourceDefinitionById(sourceId); - analyticsService.track("New Source - Action", { - action: "Select a connector", + trackNewSourceAction("Select a connector", { connector_source: sourceDefinition?.name, - connector_source_id: sourceDefinition?.sourceDefinitionId, + connector_source_definition_id: sourceDefinition?.sourceDefinitionId, }); setError(null); diff --git a/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/components/SourceForm.tsx b/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/components/SourceForm.tsx index d12423da8747..66bc00e4ce45 100644 --- a/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/components/SourceForm.tsx +++ b/airbyte-webapp/src/pages/SourcesPage/pages/CreateSourcePage/components/SourceForm.tsx @@ -4,8 +4,8 @@ import { FormattedMessage } from "react-intl"; import { ConnectionConfiguration } from "core/domain/connection"; import { SourceDefinition } from "core/domain/connector"; import { LogsRequestError } from "core/request/LogsRequestError"; -import { useAnalyticsService } from "hooks/services/Analytics/useAnalyticsService"; import useRouter from "hooks/useRouter"; +import { TrackActionType, useTrackAction } from "hooks/useTrackAction"; import { useGetSourceDefinitionSpecificationAsync } from "services/connector/SourceDefinitionSpecificationService"; import { createFormErrorMessage } from "utils/errorStatusMessage"; import { ConnectorCard } from "views/Connector/ConnectorCard"; @@ -34,7 +34,7 @@ const hasSourceDefinitionId = (state: unknown): state is { sourceDefinitionId: s const SourceForm: React.FC = ({ onSubmit, sourceDefinitions, error, hasSuccess, afterSelectConnector }) => { const { location } = useRouter(); - const analyticsService = useAnalyticsService(); + const trackNewSourceAction = useTrackAction(TrackActionType.NEW_SOURCE); const [sourceDefinitionId, setSourceDefinitionId] = useState( hasSourceDefinitionId(location.state) ? location.state.sourceDefinitionId : null @@ -54,9 +54,8 @@ const SourceForm: React.FC = ({ onSubmit, sourceDefinitions, error, hasS afterSelectConnector(); } - analyticsService.track("New Source - Action", { - action: "Select a connector", - connector_source_definition: connector?.name, + trackNewSourceAction("Select a connector", { + connector_source: connector?.name, connector_source_definition_id: sourceDefinitionId, }); }; diff --git a/airbyte-webapp/src/views/Connector/ConnectorCard/ConnectorCard.tsx b/airbyte-webapp/src/views/Connector/ConnectorCard/ConnectorCard.tsx index 952b0ac1526e..a6203af3205c 100644 --- a/airbyte-webapp/src/views/Connector/ConnectorCard/ConnectorCard.tsx +++ b/airbyte-webapp/src/views/Connector/ConnectorCard/ConnectorCard.tsx @@ -7,7 +7,7 @@ import JobItem from "components/JobItem"; import { Connector, ConnectorT, Scheduler } from "core/domain/connector"; import { JobInfo } from "core/domain/job/Job"; import { LogsRequestError } from "core/request/LogsRequestError"; -import { useAnalytics } from "hooks/services/Analytics"; +import { TrackActionType, useTrackAction } from "hooks/useTrackAction"; import { createFormErrorMessage } from "utils/errorStatusMessage"; import { ServiceForm, ServiceFormProps, ServiceFormValues } from "views/Connector/ServiceForm"; @@ -39,33 +39,46 @@ const ConnectorCard: React.FC< const { testConnector, isTestConnectionInProgress, onStopTesting, error } = useTestConnector(props); - const analyticsService = useAnalytics().service; + const trackNewSourceAction = useTrackAction(TrackActionType.NEW_SOURCE); + const trackNewDestinationAction = useTrackAction(TrackActionType.NEW_DESTINATION); const onHandleSubmit = async (values: ServiceFormValues) => { setErrorStatusRequest(null); const connector = props.availableServices.find((item) => Connector.id(item) === values.serviceType); - try { - if (connector) { - if (props.formType === "source") { - analyticsService.track("New Source - Action", { - action: "Test a connector", - connector_source: connector?.name, - connector_source_definition_id: Connector.id(connector), - }); - } else { - analyticsService.track("New Destination - Action", { - action: "Test a connector", - connector_destination: connector?.name, - connector_destination_definition_id: Connector.id(connector), - }); - } + const trackAction = (action: string) => { + if (!connector) { + return; } - await testConnector(values); - await onSubmit(values); + if (props.formType === "source") { + trackNewSourceAction(action, { + connector_source: connector?.name, + connector_source_definition_id: Connector.id(connector), + }); + } else { + trackNewDestinationAction(action, { + connector_destination: connector?.name, + connector_destination_definition_id: Connector.id(connector), + }); + } + }; + const testConnectorWithTracking = async () => { + trackAction("Test a connector"); + try { + await testConnector(values); + trackAction("Tested connector - success"); + } catch (e) { + trackAction("Tested connector - failure"); + throw e; + } + }; + + try { + await testConnectorWithTracking(); + await onSubmit(values); setSaved(true); } catch (e) { setErrorStatusRequest(e);