From 75e194b974c9b35f83d069556f06ea8a807ae42c Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Mon, 26 Aug 2024 13:56:42 -0700 Subject: [PATCH 01/11] Add all form events to v2 except errors --- .../LinodeCreatev2/Details/Details.tsx | 3 +- .../Details/PlacementGroupPanel.tsx | 33 ++++++++++- .../Linodes/LinodeCreatev2/Firewall.tsx | 56 ++++++++++++++++++- .../Linodes/LinodeCreatev2/Region.tsx | 14 ++++- .../Linodes/LinodeCreatev2/VPC/VPC.tsx | 52 ++++++++++++++++- 5 files changed, 147 insertions(+), 11 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Details/Details.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Details/Details.tsx index 7d191db7f98..82a2a9d1c95 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Details/Details.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Details/Details.tsx @@ -1,4 +1,3 @@ -import { CreateLinodeRequest } from '@linode/api-v4'; import React from 'react'; import { Controller, useFormContext } from 'react-hook-form'; @@ -12,6 +11,8 @@ import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGran import { useLinodeCreateQueryParams } from '../utilities'; import { PlacementGroupPanel } from './PlacementGroupPanel'; +import type { CreateLinodeRequest } from '@linode/api-v4'; + export const Details = () => { const { control } = useFormContext(); const { isPlacementGroupsEnabled } = useIsPlacementGroupsEnabled(); diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx index 7ab6f8ee0a5..6b36d1b8012 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx @@ -2,8 +2,13 @@ import React from 'react'; import { useController, useWatch } from 'react-hook-form'; import { PlacementGroupsDetailPanel } from 'src/features/PlacementGroups/PlacementGroupsDetailPanel'; +import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEventAnalytics'; +import { useLinodeCreateQueryParams } from '../utilities'; + +import type { LinodeCreateType } from '../../LinodesCreate/types'; import type { CreateLinodeRequest } from '@linode/api-v4'; +import type { LinodeCreateFormEventOptions } from 'src/utilities/analytics/types'; export const PlacementGroupPanel = () => { const { field } = useController({ @@ -12,11 +17,33 @@ export const PlacementGroupPanel = () => { const regionId = useWatch({ name: 'region' }); + const { params } = useLinodeCreateQueryParams(); + + const placementGroupFormEventOptions: LinodeCreateFormEventOptions = { + createType: (params.type as LinodeCreateType) ?? 'OS', + headerName: 'Details', + interaction: 'change', + label: 'Placement Group', + subheaderName: 'Placement Groups in Region', + trackOnce: true, + }; + return ( - field.onChange(placementGroup?.id) - } + handlePlacementGroupChange={(placementGroup) => { + field.onChange(placementGroup?.id); + // Track clearing and changing the value once per page view, configured by inputValue in AA backend. + if (!placementGroup?.id) { + sendLinodeCreateFormInputEvent({ + ...placementGroupFormEventOptions, + interaction: 'clear', + }); + } else { + sendLinodeCreateFormInputEvent({ + ...placementGroupFormEventOptions, + }); + } + }} selectedPlacementGroupId={field.value ?? null} selectedRegionId={regionId} /> diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx index 648dfaf9312..440a475361e 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx @@ -16,9 +16,14 @@ import { useFlags } from 'src/hooks/useFlags'; import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck'; import { useSecureVMNoticesEnabled } from 'src/hooks/useSecureVMNoticesEnabled'; import { useAllFirewallsQuery } from 'src/queries/firewalls'; +import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEventAnalytics'; +import { useLinodeCreateQueryParams } from './utilities'; + +import type { LinodeCreateType } from '../LinodesCreate/types'; import type { LinodeCreateFormValues } from './utilities'; import type { CreateLinodeRequest } from '@linode/api-v4'; +import type { LinodeCreateFormEventOptions } from 'src/utilities/analytics/types'; export const Firewall = () => { const { clearErrors } = useFormContext(); @@ -34,6 +39,8 @@ export const Firewall = () => { const flags = useFlags(); + const { params } = useLinodeCreateQueryParams(); + const { secureVMNoticesEnabled } = useSecureVMNoticesEnabled(); const secureVMFirewallBanner = (secureVMNoticesEnabled && flags.secureVmCopy) ?? false; @@ -52,6 +59,13 @@ export const Firewall = () => { field.onChange(firewallId ?? null); }; + const firewallFormEventOptions: LinodeCreateFormEventOptions = { + createType: params.type ?? 'OS', + headerName: 'Firewall', + interaction: 'click', + label: 'Firewall', + }; + return ( @@ -59,7 +73,20 @@ export const Firewall = () => { Assign an existing Firewall to this Linode to control inbound and outbound network traffic.{' '} - Learn more. + + sendLinodeCreateFormInputEvent({ + createType: (params.type as LinodeCreateType) ?? 'OS', + headerName: 'Firewall', + interaction: 'click', + label: 'Learn more', + }) + } + to={FIREWALL_GET_STARTED_LINK} + > + Learn more + + . {secureVMFirewallBanner !== false && secureVMFirewallBanner.linodeCreate && ( @@ -77,21 +104,44 @@ export const Firewall = () => { )} { + onChange(firewall?.id); // Track clearing and changing the value once per page view, configured by inputValue in AA backend. + if (!firewall?.id) { + sendLinodeCreateFormInputEvent({ + ...firewallFormEventOptions, + interaction: 'clear', + subheaderName: 'Assign Firewall', + trackOnce: true, + }); + } else { + sendLinodeCreateFormInputEvent({ + ...firewallFormEventOptions, + interaction: 'change', + subheaderName: 'Assign Firewall', + trackOnce: true, + }); + } + }} disabled={isLinodeCreateRestricted} errorText={fieldState.error?.message ?? error?.[0].reason} label="Assign Firewall" loading={isLoading} noMarginTop onBlur={field.onBlur} - onChange={(e, firewall) => onChange(firewall?.id)} options={firewalls ?? []} placeholder="None" value={selectedFirewall} /> { + setIsDrawerOpen(true); + sendLinodeCreateFormInputEvent({ + ...firewallFormEventOptions, + label: 'Create Firewall', + }); + }} isDisabled={isLinodeCreateRestricted} - onClick={() => setIsDrawerOpen(true)} > Create Firewall diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx index 9f51b4e2e41..6f45a91d820 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx @@ -20,6 +20,7 @@ import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGran import { useImageQuery } from 'src/queries/images'; import { useRegionsQuery } from 'src/queries/regions/regions'; import { useTypeQuery } from 'src/queries/types'; +import { sendLinodeCreateFormStartEvent } from 'src/utilities/analytics/formEventAnalytics'; import { DIFFERENT_PRICE_STRUCTURE_WARNING, DOCS_LINK_LABEL_DC_PRICING, @@ -150,6 +151,11 @@ export const Region = () => { setValue('label', label); } + + // Begin tracking the Linode Create form - fires once per page view, configured in AA backend. + sendLinodeCreateFormStartEvent({ + createType: params.type ?? 'OS', + }); }; const showCrossDataCenterCloneWarning = @@ -223,6 +229,13 @@ export const Region = () => { )} { + onChange(region); + // Begin tracking the Linode Create form - fires once per page view, configured in AA backend. + sendLinodeCreateFormStartEvent({ + createType: params.type ?? 'OS', + }); + }} regionFilter={ // We don't want the Image Service Gen2 work to abide by Gecko feature flags hideDistributedRegions && params.type !== 'Images' @@ -237,7 +250,6 @@ export const Region = () => { disabled={isLinodeCreateRestricted} disabledRegions={disabledRegions} errorText={fieldState.error?.message} - onChange={(e, region) => onChange(region)} regions={regions ?? []} textFieldProps={{ onBlur: field.onBlur }} value={field.value} diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/VPC/VPC.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/VPC/VPC.tsx index 7c54772b66a..24f5901cc47 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/VPC/VPC.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/VPC/VPC.tsx @@ -19,13 +19,16 @@ import { VPC_AUTO_ASSIGN_IPV4_TOOLTIP } from 'src/features/VPCs/constants'; import { inputMaxWidth } from 'src/foundations/themes/light'; import { useRegionsQuery } from 'src/queries/regions/regions'; import { useVPCQuery, useVPCsQuery } from 'src/queries/vpcs/vpcs'; +import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEventAnalytics'; import { doesRegionSupportFeature } from 'src/utilities/doesRegionSupportFeature'; import { REGION_CAVEAT_HELPER_TEXT } from '../../LinodesCreate/constants'; import { VPCCreateDrawer } from '../../LinodesCreate/VPCCreateDrawer'; +import { useLinodeCreateQueryParams } from '../utilities'; import { VPCRanges } from './VPCRanges'; import type { CreateLinodeRequest } from '@linode/api-v4'; +import type { LinodeCreateFormEventOptions } from 'src/utilities/analytics/types'; export const VPC = () => { const [isCreateDrawerOpen, setIsCreateDrawerOpen] = useState(false); @@ -72,13 +75,30 @@ export const VPC = () => { ? 'Allow Linode to communicate in an isolated environment.' : 'Assign this Linode to an existing VPC.'; + const { params } = useLinodeCreateQueryParams(); + + const vpcFormEventOptions: LinodeCreateFormEventOptions = { + createType: params.type ?? 'OS', + headerName: 'VPC', + interaction: 'click', + label: 'VPC', + }; + return ( VPC {copy}{' '} - + + sendLinodeCreateFormInputEvent({ + ...vpcFormEventOptions, + label: 'Learn more', + }) + } + to="https://www.linode.com/docs/products/networking/vpc/guides/assign-services/" + > Learn more. @@ -91,6 +111,25 @@ export const VPC = () => { ? 'VPC is not available in the selected region.' : undefined } + onChange={(e, vpc) => { + field.onChange(vpc?.id ?? null); + // Track clearing and changing the value once per page view, configured by inputValue in AA backend. + if (!vpc?.id) { + sendLinodeCreateFormInputEvent({ + ...vpcFormEventOptions, + interaction: 'clear', + subheaderName: 'Assign VPC', + trackOnce: true, + }); + } else { + sendLinodeCreateFormInputEvent({ + ...vpcFormEventOptions, + interaction: 'change', + subheaderName: 'Assign VPC', + trackOnce: true, + }); + } + }} textFieldProps={{ sx: (theme) => ({ [theme.breakpoints.up('sm')]: { minWidth: inputMaxWidth }, @@ -103,7 +142,6 @@ export const VPC = () => { label="Assign VPC" noMarginTop onBlur={field.onBlur} - onChange={(e, vpc) => field.onChange(vpc?.id ?? null)} placeholder="None" value={field.value ?? null} /> @@ -113,7 +151,15 @@ export const VPC = () => { /> {regionId && regionSupportsVPCs && ( - setIsCreateDrawerOpen(true)}> + { + setIsCreateDrawerOpen(true); + sendLinodeCreateFormInputEvent({ + ...vpcFormEventOptions, + label: 'Create VPC', + }); + }} + > Create VPC From 0cbf5623702172d3674df256c7fb9cddf7e4d8eb Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Mon, 26 Aug 2024 14:27:05 -0700 Subject: [PATCH 02/11] Add form error and form submit events --- .../features/Linodes/LinodeCreatev2/index.tsx | 47 +++++++++++++++++-- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx index fb59842afde..065de340dda 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx @@ -2,7 +2,7 @@ import { isEmpty } from '@linode/api-v4'; import * as Sentry from '@sentry/react'; import { useQueryClient } from '@tanstack/react-query'; import { useSnackbar } from 'notistack'; -import React, { useEffect, useRef } from 'react'; +import React, { useCallback, useEffect, useRef } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useHistory } from 'react-router-dom'; @@ -20,6 +20,10 @@ import { useCloneLinodeMutation, useCreateLinodeMutation, } from 'src/queries/linodes/linodes'; +import { + sendLinodeCreateFormErrorEvent, + sendLinodeCreateFormSubmitEvent, +} from 'src/utilities/analytics/formEventAnalytics'; import { scrollErrorIntoView } from 'src/utilities/scrollErrorIntoView'; import { Actions } from './Actions'; @@ -54,7 +58,7 @@ import { VLAN } from './VLAN'; import { VPC } from './VPC/VPC'; import type { LinodeCreateFormValues } from './utilities'; -import type { SubmitHandler } from 'react-hook-form'; +import type { FieldErrors, SubmitHandler } from 'react-hook-form'; export const LinodeCreatev2 = () => { const { params, setParams } = useLinodeCreateQueryParams(); @@ -117,6 +121,10 @@ export const LinodeCreatev2 = () => { values, }); + sendLinodeCreateFormSubmitEvent({ + createType: params.type, + }); + if (values.hasSignedEUAgreement) { updateAccountAgreements({ eu_model: true, @@ -134,6 +142,38 @@ export const LinodeCreatev2 = () => { } }; + const handleAnalyticsFormError = useCallback( + (errors: FieldErrors) => { + let errorString = ''; + + if (!errors) { + return; + } + + if (errors.region) { + errorString += errors.region.message; + } + if (errors.type) { + errorString += `${errorString.length > 0 ? `|` : ''}${ + errors.type.message + }`; + } + if (errors.root_pass) { + errorString += `${errorString.length > 0 ? `|` : ''}${ + errors.root_pass.message + }`; + } + if (errors.root) { + errorString += `${errorString.length > 0 ? `|` : ''}${ + errors.root.message + }`; + } + + sendLinodeCreateFormErrorEvent(errorString, params.type ?? 'OS'); + }, + [params.type] + ); + const previousSubmitCount = useRef(0); useEffect(() => { @@ -142,9 +182,10 @@ export const LinodeCreatev2 = () => { form.formState.submitCount > previousSubmitCount.current ) { scrollErrorIntoView(undefined, { behavior: 'smooth' }); + handleAnalyticsFormError(form.formState.errors); } previousSubmitCount.current = form.formState.submitCount; - }, [form.formState]); + }, [form.formState, handleAnalyticsFormError]); /** * Add a Sentry tag when Linode Create v2 is mounted From 96dd5f8532381a2e2ea619dd596056a9fa0a44ce Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Mon, 26 Aug 2024 14:43:43 -0700 Subject: [PATCH 03/11] Clean up comments --- .../manager/src/features/Linodes/LinodeCreatev2/Region.tsx | 4 ++-- .../manager/src/features/Linodes/LinodeCreatev2/VPC/VPC.tsx | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx index 6f45a91d820..f01601a7781 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx @@ -152,7 +152,7 @@ export const Region = () => { setValue('label', label); } - // Begin tracking the Linode Create form - fires once per page view, configured in AA backend. + // Begin tracking the Linode Create form. sendLinodeCreateFormStartEvent({ createType: params.type ?? 'OS', }); @@ -231,7 +231,7 @@ export const Region = () => { { onChange(region); - // Begin tracking the Linode Create form - fires once per page view, configured in AA backend. + // Begin tracking the Linode Create form. sendLinodeCreateFormStartEvent({ createType: params.type ?? 'OS', }); diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/VPC/VPC.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/VPC/VPC.tsx index 24f5901cc47..491a676b766 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/VPC/VPC.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/VPC/VPC.tsx @@ -113,7 +113,6 @@ export const VPC = () => { } onChange={(e, vpc) => { field.onChange(vpc?.id ?? null); - // Track clearing and changing the value once per page view, configured by inputValue in AA backend. if (!vpc?.id) { sendLinodeCreateFormInputEvent({ ...vpcFormEventOptions, From 1d3c6a46d93a58ae123d332cec49d9a73a2201d8 Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Mon, 26 Aug 2024 15:07:05 -0700 Subject: [PATCH 04/11] Add missing link and form event to TwoStepRegion; add test coverage --- .../LinodeCreatev2/TwoStepRegion.test.tsx | 10 ++++++-- .../Linodes/LinodeCreatev2/TwoStepRegion.tsx | 23 ++++++++++++++++++- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/TwoStepRegion.test.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/TwoStepRegion.test.tsx index bdc8bfacfec..a5477dd3c41 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/TwoStepRegion.test.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/TwoStepRegion.test.tsx @@ -1,20 +1,26 @@ import { userEvent } from '@testing-library/user-event'; import React from 'react'; +import { DOCS_LINK_LABEL_DC_PRICING } from 'src/utilities/pricing/constants'; import { renderWithThemeAndHookFormContext } from 'src/utilities/testHelpers'; import { TwoStepRegion } from './TwoStepRegion'; describe('TwoStepRegion', () => { - it('should render a heading', () => { - const { getAllByText } = renderWithThemeAndHookFormContext({ + it('should render a heading and docs link', () => { + const { getAllByText, getByText } = renderWithThemeAndHookFormContext({ component: , }); const heading = getAllByText('Region')[0]; + const link = getByText(DOCS_LINK_LABEL_DC_PRICING); expect(heading).toBeVisible(); expect(heading.tagName).toBe('H2'); + + expect(link).toBeVisible(); + expect(link).toHaveRole('link'); + expect(link).toHaveAttribute('href', 'https://www.linode.com/pricing'); }); it('should render two tabs, Core and Distributed', () => { diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/TwoStepRegion.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/TwoStepRegion.tsx index bb3a7a81cd4..dd552210b9b 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/TwoStepRegion.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/TwoStepRegion.tsx @@ -19,6 +19,10 @@ import type { RegionFilterValue, RegionSelectProps, } from 'src/components/RegionSelect/RegionSelect.types'; +import { DocsLink } from 'src/components/DocsLink/DocsLink'; +import { DOCS_LINK_LABEL_DC_PRICING } from 'src/utilities/pricing/constants'; +import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEventAnalytics'; +import { useLinodeCreateQueryParams } from './utilities'; interface GeographicalAreaOption { label: string; @@ -70,10 +74,27 @@ export const TwoStepRegion = (props: CombinedProps) => { ); const { data: regions } = useRegionsQuery(); + const { params } = useLinodeCreateQueryParams(); return ( - Region + + + Region + + + sendLinodeCreateFormInputEvent({ + createType: params.type ?? 'OS', + headerName: 'Region', + interaction: 'click', + label: DOCS_LINK_LABEL_DC_PRICING, + }) + } + href="https://www.linode.com/pricing" + label={DOCS_LINK_LABEL_DC_PRICING} + /> + Core From a4decc6a4b9ed8c2b150134ab801761b5d2ea7b0 Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Mon, 26 Aug 2024 15:07:52 -0700 Subject: [PATCH 05/11] Add missing form event on Getting Started docs link --- .../manager/src/features/Linodes/LinodeCreatev2/index.tsx | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx index 065de340dda..d8761506b60 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx @@ -22,6 +22,7 @@ import { } from 'src/queries/linodes/linodes'; import { sendLinodeCreateFormErrorEvent, + sendLinodeCreateFormInputEvent, sendLinodeCreateFormSubmitEvent, } from 'src/utilities/analytics/formEventAnalytics'; import { scrollErrorIntoView } from 'src/utilities/scrollErrorIntoView'; @@ -204,6 +205,13 @@ export const LinodeCreatev2 = () => { + sendLinodeCreateFormInputEvent({ + createType: params.type ?? 'OS', + interaction: 'click', + label: 'Getting Started', + }) + } docsLabel="Getting Started" docsLink="https://www.linode.com/docs/guides/platform/get-started/" title="Create" From a35b51a6d11479a9d1cb1ac7eeb40f1f08d89f12 Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Mon, 26 Aug 2024 15:09:05 -0700 Subject: [PATCH 06/11] Clean up from LCv1 --- .../SelectFirewallPanel.tsx | 44 ++----------------- .../Details/PlacementGroupPanel.tsx | 3 +- .../Linodes/LinodeCreatev2/Firewall.tsx | 3 +- 3 files changed, 5 insertions(+), 45 deletions(-) diff --git a/packages/manager/src/components/SelectFirewallPanel/SelectFirewallPanel.tsx b/packages/manager/src/components/SelectFirewallPanel/SelectFirewallPanel.tsx index 2111beb0af2..6a16cb8a44f 100644 --- a/packages/manager/src/components/SelectFirewallPanel/SelectFirewallPanel.tsx +++ b/packages/manager/src/components/SelectFirewallPanel/SelectFirewallPanel.tsx @@ -1,6 +1,5 @@ import { styled } from '@mui/material/styles'; import * as React from 'react'; -import { useLocation } from 'react-router-dom'; import { Box } from 'src/components/Box'; import { Paper } from 'src/components/Paper'; @@ -10,8 +9,6 @@ import { CreateFirewallDrawer } from 'src/features/Firewalls/FirewallLanding/Cre import { useFlags } from 'src/hooks/useFlags'; import { useSecureVMNoticesEnabled } from 'src/hooks/useSecureVMNoticesEnabled'; import { useFirewallsQuery } from 'src/queries/firewalls'; -import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEventAnalytics'; -import { getQueryParamsFromQueryString } from 'src/utilities/queryParams'; import { AkamaiBanner } from '../AkamaiBanner/AkamaiBanner'; import { Autocomplete } from '../Autocomplete/Autocomplete'; @@ -19,8 +16,6 @@ import { GenerateFirewallDialog } from '../GenerateFirewallDialog/GenerateFirewa import { LinkButton } from '../LinkButton'; import type { Firewall, FirewallDeviceEntityType } from '@linode/api-v4'; -import type { LinodeCreateQueryParams } from 'src/features/Linodes/types'; -import type { LinodeCreateFormEventOptions } from 'src/utilities/analytics/types'; interface Props { disabled?: boolean; @@ -41,18 +36,7 @@ export const SelectFirewallPanel = (props: Props) => { const [isDrawerOpen, setIsDrawerOpen] = React.useState(false); const [isFirewallDialogOpen, setIsFirewallDialogOpen] = React.useState(false); - const location = useLocation(); - const isFromLinodeCreate = location.pathname.includes('/linodes/create'); - const queryParams = getQueryParamsFromQueryString( - location.search - ); - const firewallFormEventOptions: LinodeCreateFormEventOptions = { - createType: queryParams.type ?? 'OS', - headerName: 'Firewall', - interaction: 'click', - label: 'Firewall', - }; const flags = useFlags(); const { secureVMNoticesEnabled } = useSecureVMNoticesEnabled(); @@ -61,12 +45,6 @@ export const SelectFirewallPanel = (props: Props) => { const handleCreateFirewallClick = () => { setIsDrawerOpen(true); - if (isFromLinodeCreate) { - sendLinodeCreateFormInputEvent({ - ...firewallFormEventOptions, - label: 'Create Firewall', - }); - } }; const handleFirewallCreated = (firewall: Firewall) => { @@ -116,25 +94,9 @@ export const SelectFirewallPanel = (props: Props) => { /> )} { - handleFirewallChange(selection?.value ?? -1); - // Track clearing and changing the value once per page view, configured by inputValue in AA backend. - if (!selection) { - sendLinodeCreateFormInputEvent({ - ...firewallFormEventOptions, - interaction: 'clear', - subheaderName: 'Assign Firewall', - trackOnce: true, - }); - } else { - sendLinodeCreateFormInputEvent({ - ...firewallFormEventOptions, - interaction: 'change', - subheaderName: 'Assign Firewall', - trackOnce: true, - }); - } - }} + onChange={(_, selection) => + handleFirewallChange(selection?.value ?? -1) + } disabled={disabled} errorText={error?.[0].reason} label="Assign Firewall" diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx index 6b36d1b8012..7e0aa2cb8bb 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx @@ -6,7 +6,6 @@ import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEven import { useLinodeCreateQueryParams } from '../utilities'; -import type { LinodeCreateType } from '../../LinodesCreate/types'; import type { CreateLinodeRequest } from '@linode/api-v4'; import type { LinodeCreateFormEventOptions } from 'src/utilities/analytics/types'; @@ -20,7 +19,7 @@ export const PlacementGroupPanel = () => { const { params } = useLinodeCreateQueryParams(); const placementGroupFormEventOptions: LinodeCreateFormEventOptions = { - createType: (params.type as LinodeCreateType) ?? 'OS', + createType: params.type ?? 'OS', headerName: 'Details', interaction: 'change', label: 'Placement Group', diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx index 440a475361e..c9374721c75 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx @@ -20,7 +20,6 @@ import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEven import { useLinodeCreateQueryParams } from './utilities'; -import type { LinodeCreateType } from '../LinodesCreate/types'; import type { LinodeCreateFormValues } from './utilities'; import type { CreateLinodeRequest } from '@linode/api-v4'; import type { LinodeCreateFormEventOptions } from 'src/utilities/analytics/types'; @@ -76,7 +75,7 @@ export const Firewall = () => { sendLinodeCreateFormInputEvent({ - createType: (params.type as LinodeCreateType) ?? 'OS', + createType: params.type ?? 'OS', headerName: 'Firewall', interaction: 'click', label: 'Learn more', From defaeeed38d07063a905ad996b63e052515d7f1b Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Mon, 26 Aug 2024 15:26:20 -0700 Subject: [PATCH 07/11] Fix Region, Plan, and CLI button events --- .../src/features/Linodes/LinodeCreatev2/Actions.tsx | 8 ++++++++ .../LinodeCreatev2/Details/PlacementGroupPanel.tsx | 1 - .../features/Linodes/LinodeCreatev2/Firewall.tsx | 2 +- .../src/features/Linodes/LinodeCreatev2/Plan.tsx | 10 ++++++++++ .../src/features/Linodes/LinodeCreatev2/Region.tsx | 13 ++++++++++++- .../Linodes/LinodeCreatev2/TwoStepRegion.tsx | 13 ++++++------- 6 files changed, 37 insertions(+), 10 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Actions.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Actions.tsx index fc382cc1d20..53b5fd1be3b 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Actions.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Actions.tsx @@ -6,6 +6,7 @@ import { Button } from 'src/components/Button/Button'; import { useFlags } from 'src/hooks/useFlags'; import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck'; import { sendApiAwarenessClickEvent } from 'src/utilities/analytics/customEventAnalytics'; +import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEventAnalytics'; import { scrollErrorIntoView } from 'src/utilities/scrollErrorIntoView'; import { ApiAwarenessModal } from '../LinodesCreate/ApiAwarenessModal/ApiAwarenessModal'; @@ -35,6 +36,13 @@ export const Actions = () => { const onOpenAPIAwareness = async () => { sendApiAwarenessClickEvent('Button', 'Create Using Command Line'); + sendLinodeCreateFormInputEvent({ + createType: 'OS', + interaction: 'click', + label: isDxToolsAdditionsEnabled + ? 'View Code Snippets' + : 'Create Using Command Line', + }); if (await trigger()) { // If validation is successful, we open the dialog. setIsAPIAwarenessModalOpen(true); diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx index 7e0aa2cb8bb..9e59916bc3e 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx @@ -31,7 +31,6 @@ export const PlacementGroupPanel = () => { { field.onChange(placementGroup?.id); - // Track clearing and changing the value once per page view, configured by inputValue in AA backend. if (!placementGroup?.id) { sendLinodeCreateFormInputEvent({ ...placementGroupFormEventOptions, diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx index c9374721c75..374303ff6b9 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx @@ -104,7 +104,7 @@ export const Firewall = () => { { - onChange(firewall?.id); // Track clearing and changing the value once per page view, configured by inputValue in AA backend. + onChange(firewall?.id); if (!firewall?.id) { sendLinodeCreateFormInputEvent({ ...firewallFormEventOptions, diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Plan.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Plan.tsx index 4df3406c4da..b76352ed44d 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Plan.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Plan.tsx @@ -7,8 +7,11 @@ import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGran import { useRegionsQuery } from 'src/queries/regions/regions'; import { useAllTypes } from 'src/queries/types'; import { sendLinodeCreateFlowDocsClickEvent } from 'src/utilities/analytics/customEventAnalytics'; +import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEventAnalytics'; import { extendType } from 'src/utilities/extendType'; +import { useLinodeCreateQueryParams } from './utilities'; + import type { LinodeCreateFormValues } from './utilities'; import type { CreateLinodeRequest } from '@linode/api-v4'; @@ -22,6 +25,7 @@ export const Plan = () => { const { data: regions } = useRegionsQuery(); const { data: types } = useAllTypes(); + const { params } = useLinodeCreateQueryParams(); const isLinodeCreateRestricted = useRestrictedGlobalGrantCheck({ globalGrantType: 'add_linodes', @@ -33,6 +37,12 @@ export const Plan = () => { { sendLinodeCreateFlowDocsClickEvent('Choosing a Plan'); + sendLinodeCreateFormInputEvent({ + createType: params.type ?? 'OS', + headerName: 'Linode Plan', + interaction: 'click', + label: 'Choosing a Plan', + }); }} href="https://www.linode.com/docs/guides/choosing-a-compute-instance-plan/" label="Choosing a Plan" diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx index f01601a7781..25ec0c9d65f 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx @@ -20,7 +20,10 @@ import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGran import { useImageQuery } from 'src/queries/images'; import { useRegionsQuery } from 'src/queries/regions/regions'; import { useTypeQuery } from 'src/queries/types'; -import { sendLinodeCreateFormStartEvent } from 'src/utilities/analytics/formEventAnalytics'; +import { + sendLinodeCreateFormInputEvent, + sendLinodeCreateFormStartEvent, +} from 'src/utilities/analytics/formEventAnalytics'; import { DIFFERENT_PRICE_STRUCTURE_WARNING, DOCS_LINK_LABEL_DC_PRICING, @@ -216,6 +219,14 @@ export const Region = () => { Region + sendLinodeCreateFormInputEvent({ + createType: params.type ?? 'OS', + headerName: 'Region', + interaction: 'click', + label: DOCS_LINK_LABEL_DC_PRICING, + }) + } href="https://www.linode.com/pricing" label={DOCS_LINK_LABEL_DC_PRICING} /> diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/TwoStepRegion.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/TwoStepRegion.tsx index dd552210b9b..67b8fac49dd 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/TwoStepRegion.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/TwoStepRegion.tsx @@ -2,6 +2,7 @@ import * as React from 'react'; import { Autocomplete } from 'src/components/Autocomplete/Autocomplete'; import { Box } from 'src/components/Box'; +import { DocsLink } from 'src/components/DocsLink/DocsLink'; import { Paper } from 'src/components/Paper'; import { RegionSelect } from 'src/components/RegionSelect/RegionSelect'; import { RegionHelperText } from 'src/components/SelectRegionPanel/RegionHelperText'; @@ -13,16 +14,16 @@ import { Tabs } from 'src/components/Tabs/Tabs'; import { Typography } from 'src/components/Typography'; import { useRegionsQuery } from 'src/queries/regions/regions'; import { sendLinodeCreateDocsEvent } from 'src/utilities/analytics/customEventAnalytics'; +import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEventAnalytics'; +import { DOCS_LINK_LABEL_DC_PRICING } from 'src/utilities/pricing/constants'; + +import { useLinodeCreateQueryParams } from './utilities'; import type { Region as RegionType } from '@linode/api-v4'; import type { RegionFilterValue, RegionSelectProps, } from 'src/components/RegionSelect/RegionSelect.types'; -import { DocsLink } from 'src/components/DocsLink/DocsLink'; -import { DOCS_LINK_LABEL_DC_PRICING } from 'src/utilities/pricing/constants'; -import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEventAnalytics'; -import { useLinodeCreateQueryParams } from './utilities'; interface GeographicalAreaOption { label: string; @@ -79,9 +80,7 @@ export const TwoStepRegion = (props: CombinedProps) => { return ( - - Region - + Region sendLinodeCreateFormInputEvent({ From ccab87d167cf61fa1fdc31313d68f6664ae78f34 Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Mon, 26 Aug 2024 15:56:05 -0700 Subject: [PATCH 08/11] Fix double fire on Region; fix formSubmit on OS --- .../src/features/Linodes/LinodeCreatev2/Region.tsx | 8 +------- .../manager/src/features/Linodes/LinodeCreatev2/index.tsx | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx index 25ec0c9d65f..44974328513 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx @@ -240,13 +240,6 @@ export const Region = () => { )} { - onChange(region); - // Begin tracking the Linode Create form. - sendLinodeCreateFormStartEvent({ - createType: params.type ?? 'OS', - }); - }} regionFilter={ // We don't want the Image Service Gen2 work to abide by Gecko feature flags hideDistributedRegions && params.type !== 'Images' @@ -261,6 +254,7 @@ export const Region = () => { disabled={isLinodeCreateRestricted} disabledRegions={disabledRegions} errorText={fieldState.error?.message} + onChange={(e, region) => onChange(region)} regions={regions ?? []} textFieldProps={{ onBlur: field.onBlur }} value={field.value} diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx index d8761506b60..25abe8a55a5 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx @@ -123,7 +123,7 @@ export const LinodeCreatev2 = () => { }); sendLinodeCreateFormSubmitEvent({ - createType: params.type, + createType: params.type ?? 'OS', }); if (values.hasSignedEUAgreement) { From fd595c3984862001de3ae10b9e43f784bd0e15ee Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Mon, 26 Aug 2024 16:00:35 -0700 Subject: [PATCH 09/11] Added changeset: Tag Linode Create v2 with form events --- .../.changeset/pr-10840-tech-stories-1724713234913.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/manager/.changeset/pr-10840-tech-stories-1724713234913.md diff --git a/packages/manager/.changeset/pr-10840-tech-stories-1724713234913.md b/packages/manager/.changeset/pr-10840-tech-stories-1724713234913.md new file mode 100644 index 00000000000..79a55ef77ca --- /dev/null +++ b/packages/manager/.changeset/pr-10840-tech-stories-1724713234913.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tech Stories +--- + +Tag Linode Create v2 with form events ([#10840](https://github.com/linode/manager/pull/10840)) From 03e134efb107ca486f6fe93a2738719e31f87070 Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Tue, 27 Aug 2024 07:19:21 -0700 Subject: [PATCH 10/11] Address feedback: remove spread and properly tag CLI button --- .../src/features/Linodes/LinodeCreatev2/Actions.tsx | 9 +++++++-- .../LinodeCreatev2/Details/PlacementGroupPanel.tsx | 4 +--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Actions.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Actions.tsx index 53b5fd1be3b..468d4f6e606 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Actions.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Actions.tsx @@ -10,13 +10,18 @@ import { sendLinodeCreateFormInputEvent } from 'src/utilities/analytics/formEven import { scrollErrorIntoView } from 'src/utilities/scrollErrorIntoView'; import { ApiAwarenessModal } from '../LinodesCreate/ApiAwarenessModal/ApiAwarenessModal'; -import { getLinodeCreatePayload } from './utilities'; +import { + getLinodeCreatePayload, + useLinodeCreateQueryParams, +} from './utilities'; import type { LinodeCreateFormValues } from './utilities'; export const Actions = () => { const flags = useFlags(); + const { params } = useLinodeCreateQueryParams(); + const [isAPIAwarenessModalOpen, setIsAPIAwarenessModalOpen] = useState(false); const isDxToolsAdditionsEnabled = flags?.apicliDxToolsAdditions; @@ -37,7 +42,7 @@ export const Actions = () => { const onOpenAPIAwareness = async () => { sendApiAwarenessClickEvent('Button', 'Create Using Command Line'); sendLinodeCreateFormInputEvent({ - createType: 'OS', + createType: params.type ?? 'OS', interaction: 'click', label: isDxToolsAdditionsEnabled ? 'View Code Snippets' diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx index 9e59916bc3e..d9ebe1481b7 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx @@ -37,9 +37,7 @@ export const PlacementGroupPanel = () => { interaction: 'clear', }); } else { - sendLinodeCreateFormInputEvent({ - ...placementGroupFormEventOptions, - }); + sendLinodeCreateFormInputEvent(placementGroupFormEventOptions); } }} selectedPlacementGroupId={field.value ?? null} From bd72f506014735142cd976be688dfbd8ad0d9fb0 Mon Sep 17 00:00:00 2001 From: mjac0bs Date: Tue, 27 Aug 2024 08:20:30 -0700 Subject: [PATCH 11/11] Address feedback: use hook to keep index file clean --- .../features/Linodes/LinodeCreatev2/index.tsx | 46 ++++--------------- .../Linodes/LinodeCreatev2/utilities.ts | 44 ++++++++++++++++++ 2 files changed, 53 insertions(+), 37 deletions(-) diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx index 25abe8a55a5..0a7e7524887 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx @@ -2,7 +2,7 @@ import { isEmpty } from '@linode/api-v4'; import * as Sentry from '@sentry/react'; import { useQueryClient } from '@tanstack/react-query'; import { useSnackbar } from 'notistack'; -import React, { useCallback, useEffect, useRef } from 'react'; +import React, { useEffect, useRef } from 'react'; import { FormProvider, useForm } from 'react-hook-form'; import { useHistory } from 'react-router-dom'; @@ -21,7 +21,6 @@ import { useCreateLinodeMutation, } from 'src/queries/linodes/linodes'; import { - sendLinodeCreateFormErrorEvent, sendLinodeCreateFormInputEvent, sendLinodeCreateFormSubmitEvent, } from 'src/utilities/analytics/formEventAnalytics'; @@ -53,13 +52,14 @@ import { getLinodeCreatePayload, getTabIndex, tabs, + useHandleLinodeCreateAnalyticsFormError, useLinodeCreateQueryParams, } from './utilities'; import { VLAN } from './VLAN'; import { VPC } from './VPC/VPC'; import type { LinodeCreateFormValues } from './utilities'; -import type { FieldErrors, SubmitHandler } from 'react-hook-form'; +import type { SubmitHandler } from 'react-hook-form'; export const LinodeCreatev2 = () => { const { params, setParams } = useLinodeCreateQueryParams(); @@ -83,6 +83,10 @@ export const LinodeCreatev2 = () => { const { mutateAsync: cloneLinode } = useCloneLinodeMutation(); const { mutateAsync: updateAccountAgreements } = useMutateAccountAgreements(); + const { + handleLinodeCreateAnalyticsFormError, + } = useHandleLinodeCreateAnalyticsFormError(params.type ?? 'OS'); + const currentTabIndex = getTabIndex(params.type); const onTabChange = (index: number) => { @@ -143,38 +147,6 @@ export const LinodeCreatev2 = () => { } }; - const handleAnalyticsFormError = useCallback( - (errors: FieldErrors) => { - let errorString = ''; - - if (!errors) { - return; - } - - if (errors.region) { - errorString += errors.region.message; - } - if (errors.type) { - errorString += `${errorString.length > 0 ? `|` : ''}${ - errors.type.message - }`; - } - if (errors.root_pass) { - errorString += `${errorString.length > 0 ? `|` : ''}${ - errors.root_pass.message - }`; - } - if (errors.root) { - errorString += `${errorString.length > 0 ? `|` : ''}${ - errors.root.message - }`; - } - - sendLinodeCreateFormErrorEvent(errorString, params.type ?? 'OS'); - }, - [params.type] - ); - const previousSubmitCount = useRef(0); useEffect(() => { @@ -183,10 +155,10 @@ export const LinodeCreatev2 = () => { form.formState.submitCount > previousSubmitCount.current ) { scrollErrorIntoView(undefined, { behavior: 'smooth' }); - handleAnalyticsFormError(form.formState.errors); + handleLinodeCreateAnalyticsFormError(form.formState.errors); } previousSubmitCount.current = form.formState.submitCount; - }, [form.formState, handleAnalyticsFormError]); + }, [form.formState, handleLinodeCreateAnalyticsFormError]); /** * Add a Sentry tag when Linode Create v2 is mounted diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/utilities.ts b/packages/manager/src/features/Linodes/LinodeCreatev2/utilities.ts index 2dcce778c29..b0c92b33886 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/utilities.ts +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/utilities.ts @@ -1,10 +1,12 @@ import { omit } from 'lodash'; +import { useCallback } from 'react'; import { useHistory } from 'react-router-dom'; import { imageQueries } from 'src/queries/images'; import { linodeQueries } from 'src/queries/linodes/linodes'; import { stackscriptQueries } from 'src/queries/stackscripts'; import { sendCreateLinodeEvent } from 'src/utilities/analytics/customEventAnalytics'; +import { sendLinodeCreateFormErrorEvent } from 'src/utilities/analytics/formEventAnalytics'; import { privateIPRegex } from 'src/utilities/ipUtils'; import { isNotNullOrUndefined } from 'src/utilities/nullOrUndefined'; import { getQueryParamsFromQueryString } from 'src/utilities/queryParams'; @@ -20,6 +22,7 @@ import type { Linode, } from '@linode/api-v4'; import type { QueryClient } from '@tanstack/react-query'; +import type { FieldErrors } from 'react-hook-form'; /** * This is the ID of the Image of the default OS. @@ -502,3 +505,44 @@ export const captureLinodeCreateAnalyticsEvent = async ( }); } }; + +/** + * Custom hook to send a Adobe Analytics form error event with error messages in the Linode Create flow. + */ +export const useHandleLinodeCreateAnalyticsFormError = ( + createType: LinodeCreateType +) => { + const handleLinodeCreateAnalyticsFormError = useCallback( + (errors: FieldErrors) => { + let errorString = ''; + + if (!errors) { + return; + } + + if (errors.region) { + errorString += errors.region.message; + } + if (errors.type) { + errorString += `${errorString.length > 0 ? `|` : ''}${ + errors.type.message + }`; + } + if (errors.root_pass) { + errorString += `${errorString.length > 0 ? `|` : ''}${ + errors.root_pass.message + }`; + } + if (errors.root) { + errorString += `${errorString.length > 0 ? `|` : ''}${ + errors.root.message + }`; + } + + sendLinodeCreateFormErrorEvent(errorString, createType); + }, + [createType] + ); + + return { handleLinodeCreateAnalyticsFormError }; +};