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)) 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/Actions.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/Actions.tsx index fc382cc1d20..468d4f6e606 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Actions.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Actions.tsx @@ -6,16 +6,22 @@ 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'; -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; @@ -35,6 +41,13 @@ export const Actions = () => { const onOpenAPIAwareness = async () => { sendApiAwarenessClickEvent('Button', 'Create Using Command Line'); + sendLinodeCreateFormInputEvent({ + createType: params.type ?? '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/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..d9ebe1481b7 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Details/PlacementGroupPanel.tsx @@ -2,8 +2,12 @@ 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 { CreateLinodeRequest } from '@linode/api-v4'; +import type { LinodeCreateFormEventOptions } from 'src/utilities/analytics/types'; export const PlacementGroupPanel = () => { const { field } = useController({ @@ -12,11 +16,30 @@ export const PlacementGroupPanel = () => { const regionId = useWatch({ name: 'region' }); + const { params } = useLinodeCreateQueryParams(); + + const placementGroupFormEventOptions: LinodeCreateFormEventOptions = { + createType: params.type ?? '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); + 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..374303ff6b9 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Firewall.tsx @@ -16,9 +16,13 @@ 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 { 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 +38,8 @@ export const Firewall = () => { const flags = useFlags(); + const { params } = useLinodeCreateQueryParams(); + const { secureVMNoticesEnabled } = useSecureVMNoticesEnabled(); const secureVMFirewallBanner = (secureVMNoticesEnabled && flags.secureVmCopy) ?? false; @@ -52,6 +58,13 @@ export const Firewall = () => { field.onChange(firewallId ?? null); }; + const firewallFormEventOptions: LinodeCreateFormEventOptions = { + createType: params.type ?? 'OS', + headerName: 'Firewall', + interaction: 'click', + label: 'Firewall', + }; + return ( @@ -59,7 +72,20 @@ export const Firewall = () => { Assign an existing Firewall to this Linode to control inbound and outbound network traffic.{' '} - Learn more. + + sendLinodeCreateFormInputEvent({ + createType: params.type ?? 'OS', + headerName: 'Firewall', + interaction: 'click', + label: 'Learn more', + }) + } + to={FIREWALL_GET_STARTED_LINK} + > + Learn more + + . {secureVMFirewallBanner !== false && secureVMFirewallBanner.linodeCreate && ( @@ -77,21 +103,44 @@ export const Firewall = () => { )} { + onChange(firewall?.id); + 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/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 9f51b4e2e41..44974328513 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/Region.tsx @@ -20,6 +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 { + sendLinodeCreateFormInputEvent, + sendLinodeCreateFormStartEvent, +} from 'src/utilities/analytics/formEventAnalytics'; import { DIFFERENT_PRICE_STRUCTURE_WARNING, DOCS_LINK_LABEL_DC_PRICING, @@ -150,6 +154,11 @@ export const Region = () => { setValue('label', label); } + + // Begin tracking the Linode Create form. + sendLinodeCreateFormStartEvent({ + createType: params.type ?? 'OS', + }); }; const showCrossDataCenterCloneWarning = @@ -210,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.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..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,6 +14,10 @@ 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 { @@ -70,10 +75,25 @@ 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 diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/VPC/VPC.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/VPC/VPC.tsx index 7c54772b66a..491a676b766 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,24 @@ export const VPC = () => { ? 'VPC is not available in the selected region.' : undefined } + onChange={(e, vpc) => { + field.onChange(vpc?.id ?? null); + 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 +141,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 +150,15 @@ export const VPC = () => { /> {regionId && regionSupportsVPCs && ( - setIsCreateDrawerOpen(true)}> + { + setIsCreateDrawerOpen(true); + sendLinodeCreateFormInputEvent({ + ...vpcFormEventOptions, + label: 'Create VPC', + }); + }} + > Create VPC diff --git a/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx b/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx index fb59842afde..0a7e7524887 100644 --- a/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreatev2/index.tsx @@ -20,6 +20,10 @@ import { useCloneLinodeMutation, useCreateLinodeMutation, } from 'src/queries/linodes/linodes'; +import { + sendLinodeCreateFormInputEvent, + sendLinodeCreateFormSubmitEvent, +} from 'src/utilities/analytics/formEventAnalytics'; import { scrollErrorIntoView } from 'src/utilities/scrollErrorIntoView'; import { Actions } from './Actions'; @@ -48,6 +52,7 @@ import { getLinodeCreatePayload, getTabIndex, tabs, + useHandleLinodeCreateAnalyticsFormError, useLinodeCreateQueryParams, } from './utilities'; import { VLAN } from './VLAN'; @@ -78,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) => { @@ -117,6 +126,10 @@ export const LinodeCreatev2 = () => { values, }); + sendLinodeCreateFormSubmitEvent({ + createType: params.type ?? 'OS', + }); + if (values.hasSignedEUAgreement) { updateAccountAgreements({ eu_model: true, @@ -142,9 +155,10 @@ export const LinodeCreatev2 = () => { form.formState.submitCount > previousSubmitCount.current ) { scrollErrorIntoView(undefined, { behavior: 'smooth' }); + handleLinodeCreateAnalyticsFormError(form.formState.errors); } previousSubmitCount.current = form.formState.submitCount; - }, [form.formState]); + }, [form.formState, handleLinodeCreateAnalyticsFormError]); /** * Add a Sentry tag when Linode Create v2 is mounted @@ -163,6 +177,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" 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 }; +};