From c76e0bd61e222cdba2bf0873c89b0994b8fd1d95 Mon Sep 17 00:00:00 2001 From: Mohammed Hamdoune Date: Thu, 17 Oct 2024 17:32:56 +0200 Subject: [PATCH] feat(pci-load-balancer): tracking ref: DTCORE-2668 Signed-off-by: Mohammed Hamdoune --- .../apps/pci-load-balancer/package.json | 1 + .../pci-load-balancer/src/api/data/flavors.ts | 6 +- .../src/api/data/load-balancer.ts | 2 +- .../src/api/hook/useFlavors.ts | 10 +- .../apps/pci-load-balancer/src/constants.ts | 22 +++ .../src/hooks/useTranslatedLinkReference.ts | 36 +++++ .../src/pages/create/Create.page.tsx | 135 +++++++++++++++--- yarn.lock | 5 + 8 files changed, 186 insertions(+), 31 deletions(-) create mode 100644 packages/manager/apps/pci-load-balancer/src/hooks/useTranslatedLinkReference.ts diff --git a/packages/manager/apps/pci-load-balancer/package.json b/packages/manager/apps/pci-load-balancer/package.json index 0922ab1919f0..dc2d5a7a90bb 100644 --- a/packages/manager/apps/pci-load-balancer/package.json +++ b/packages/manager/apps/pci-load-balancer/package.json @@ -42,6 +42,7 @@ "react-i18next": "^14.1.2", "react-router-dom": "^6.24.1", "react-use": "^17.5.0", + "uuid": "^10.0.0", "zustand": "^4.5.4" }, "devDependencies": { diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/flavors.ts b/packages/manager/apps/pci-load-balancer/src/api/data/flavors.ts index 389bfa922320..8060da2c74d8 100644 --- a/packages/manager/apps/pci-load-balancer/src/api/data/flavors.ts +++ b/packages/manager/apps/pci-load-balancer/src/api/data/flavors.ts @@ -1,17 +1,17 @@ import { v6 } from '@ovh-ux/manager-core-api'; -import { TPlan } from '@/pages/create/store'; +import { TAddon } from '@/pages/create/store'; import { TFlavor } from '@/api/data/load-balancer'; export const getFlavor = async ( projectId: string, regionName: string, - size: TPlan, + addon: TAddon, ): Promise => { const { data } = await v6.get( `/cloud/project/${projectId}/region/${regionName}/loadbalancing/flavor`, ); return data.find( - (regionalizedFlavors) => regionalizedFlavors.name === size.technicalName, + (regionalizedFlavors) => regionalizedFlavors.name === addon.technicalName, ); }; diff --git a/packages/manager/apps/pci-load-balancer/src/api/data/load-balancer.ts b/packages/manager/apps/pci-load-balancer/src/api/data/load-balancer.ts index 9cdeabbcfce6..a55a15de594d 100644 --- a/packages/manager/apps/pci-load-balancer/src/api/data/load-balancer.ts +++ b/packages/manager/apps/pci-load-balancer/src/api/data/load-balancer.ts @@ -5,10 +5,10 @@ import { FLOATING_IP_TYPE, PROTOCOLS, } from '@/constants'; -import { TRegion } from '@/api/hook/usePlans'; import { TPrivateNetwork, TSubnet } from '@/api/data/network'; import { ListenerConfiguration } from '@/components/create/InstanceTable.component'; import { TFloatingIp } from '@/api/data/floating-ips'; +import { TRegion } from '@/api/hook/useRegions'; export enum LoadBalancerOperatingStatusEnum { ONLINE = 'online', diff --git a/packages/manager/apps/pci-load-balancer/src/api/hook/useFlavors.ts b/packages/manager/apps/pci-load-balancer/src/api/hook/useFlavors.ts index 818011b4d10b..4652ab31ab44 100644 --- a/packages/manager/apps/pci-load-balancer/src/api/hook/useFlavors.ts +++ b/packages/manager/apps/pci-load-balancer/src/api/hook/useFlavors.ts @@ -1,11 +1,11 @@ import { useQuery } from '@tanstack/react-query'; -import { TPlan } from '@/pages/create/store'; import { getFlavor } from '@/api/data/flavors'; +import { TAddon } from '@/pages/create/store'; export const useGetFlavor = ( projectId: string, regionName: string, - size: TPlan, + addon: TAddon, ) => useQuery({ queryKey: [ @@ -14,10 +14,10 @@ export const useGetFlavor = ( 'region', regionName, 'size', - size?.code, + addon?.code, 'flavor', ], - queryFn: () => getFlavor(projectId, regionName, size), - enabled: !!projectId && !!regionName && !!size, + queryFn: () => getFlavor(projectId, regionName, addon), + enabled: !!projectId && !!regionName && !!addon, throwOnError: true, }); diff --git a/packages/manager/apps/pci-load-balancer/src/constants.ts b/packages/manager/apps/pci-load-balancer/src/constants.ts index ecb57e3ce336..2cf18fce0d5e 100644 --- a/packages/manager/apps/pci-load-balancer/src/constants.ts +++ b/packages/manager/apps/pci-load-balancer/src/constants.ts @@ -216,6 +216,28 @@ export const FLOATING_IP_CREATE_DESCRIPTION = 'FIP created by OVHCloud Control Panel (Manager) for Load Balancer'; export const AGORA_GATEWAY_REGEX = /gateway.s.hour.consumption/; +export const TRACKING_NAME = + 'pci::projects::project::octavia-loadbalancer::add'; +const TRACKING_ROOT = `PublicCloud::${TRACKING_NAME}`; +export const LOAD_BALANCER_CREATION_TRACKING = { + ROOT: TRACKING_ROOT, + GO_TO_PRODUCT_PAGE: `${TRACKING_ROOT}::goto-product-page`, + GO_TO_REGION_AVAILABILITY: `${TRACKING_ROOT}::goto-region-availability`, + CREATE_PRIVATE_NETWORK: `${TRACKING_ROOT}::create-private-network`, + GO_TO_INSTANCE_DOCUMENTATION: `${TRACKING_ROOT}::goto-documentation`, + CANCEL: `${TRACKING_ROOT}::cancel`, + SUBMIT: `${TRACKING_ROOT}::confirm`, + CONFIRM: `octavia-loadbalancer::confirm-creation`, + ERROR: `${TRACKING_ROOT}-error`, + SUCCESS: `${TRACKING_ROOT}-success`, + FINISH_STEP_1: 'loadbalancer_octavia_add_size', + FINISH_STEP_2: 'loadbalancer_octavia_add_region', + FINISH_STEP_3: 'loadbalancer_octavia_add_ip', + FINISH_STEP_4: 'loadbalancer_octavia_add_network', + FINISH_STEP_5: 'loadbalancer_octavia_add_instances', + SKIP_STEP_5: 'loadbalancer_octavia_add_instances_skip', +}; + export const RULE_TYPES = { COOKIE: 'cookie', FILE_TYPE: 'fileType', diff --git a/packages/manager/apps/pci-load-balancer/src/hooks/useTranslatedLinkReference.ts b/packages/manager/apps/pci-load-balancer/src/hooks/useTranslatedLinkReference.ts new file mode 100644 index 000000000000..d383046e7c09 --- /dev/null +++ b/packages/manager/apps/pci-load-balancer/src/hooks/useTranslatedLinkReference.ts @@ -0,0 +1,36 @@ +import { useContext, useEffect, useRef } from 'react'; +import { ShellContext } from '@ovh-ux/manager-react-shell-client'; + +export const useTranslatedLinkReference = () => { + const { tracking } = useContext(ShellContext).shell; + const ref = useRef(null); + + useEffect(() => { + if (ref.current) { + const anchors = ref.current.querySelectorAll('a'); + anchors.forEach((anchor) => { + const { trackName, trackOn, trackType, handled } = anchor.dataset; + if (!handled) { + anchor.classList.add( + 'font-bold', + 'no-underline', + 'text-blue-600', + 'hover:underline', + ); + + anchor.setAttribute('data-handled', 'true'); + if (trackOn === 'click') { + anchor.addEventListener('click', () => { + tracking.trackClick({ + name: trackName, + type: trackType || 'action', + }); + }); + } + } + }); + } + }, [ref.current]); + + return ref; +}; diff --git a/packages/manager/apps/pci-load-balancer/src/pages/create/Create.page.tsx b/packages/manager/apps/pci-load-balancer/src/pages/create/Create.page.tsx index 2e7cdc3a753e..8e63a67e5ef5 100644 --- a/packages/manager/apps/pci-load-balancer/src/pages/create/Create.page.tsx +++ b/packages/manager/apps/pci-load-balancer/src/pages/create/Create.page.tsx @@ -13,12 +13,12 @@ import { import { Headers, Notifications, - StepComponent, TilesInputComponent, useCatalogPrice, useMe, useNotifications, useProjectUrl, + StepComponent, } from '@ovh-ux/manager-react-components'; import { useHref, useNavigate, useParams } from 'react-router-dom'; import { useCatalog, useProject } from '@ovh-ux/manager-pci-common'; @@ -35,14 +35,22 @@ import { ODS_THEME_TYPOGRAPHY_SIZE, } from '@ovhcloud/ods-common-theming'; import { clsx } from 'clsx'; -import React, { useEffect, useMemo, useState } from 'react'; +import React, { + useCallback, + useContext, + useEffect, + useMemo, + useState, +} from 'react'; import { ApiError } from '@ovh-ux/manager-core-api'; +import { ShellContext } from '@ovh-ux/manager-react-shell-client'; import SizeInputComponent from '@/pages/create/SizeInput.component'; import { AGORA_FLOATING_IP_REGEX, AGORA_GATEWAY_REGEX, FLOATING_IP_TYPE, GETTING_STARTED_LINK, + LOAD_BALANCER_CREATION_TRACKING, LOAD_BALANCER_NAME_REGEX, MAX_INSTANCES_BY_LISTENER, MAX_LISTENER, @@ -61,6 +69,7 @@ import { useGetFloatingIps } from '@/api/hook/useFloatingIps'; import { useGetSubnetGateways } from '@/api/hook/useGateways'; import { useGetFlavor } from '@/api/hook/useFlavors'; import { useGetAddons } from '@/api/hook/useAddons'; +import { useTranslatedLinkReference } from '@/hooks/useTranslatedLinkReference'; type TState = { selectedContinent: string | undefined; @@ -68,6 +77,20 @@ type TState = { }; export default function CreatePage(): JSX.Element { + const { tracking } = useContext(ShellContext).shell; + const instanceTrack = useTranslatedLinkReference(); + const networkTrack = useTranslatedLinkReference(); + + const trackStep = useCallback( + (step: number) => { + tracking.trackClick({ + name: LOAD_BALANCER_CREATION_TRACKING[`FINISH_STEP_${step}`], + type: 'action', + }); + }, + [tracking], + ); + const navigate = useNavigate(); const { addSuccess, addError } = useNotifications(); @@ -177,13 +200,6 @@ export default function CreatePage(): JSX.Element { }, [subnets, store.publicIp]), ]; - const [productPageLink, regionPageLink, gettingStartedLink] = [ - PRODUCT_LINK[me?.ovhSubsidiary] || PRODUCT_LINK.DEFAULT, - REGION_AVAILABILITY_LINK[me?.ovhSubsidiary] || - REGION_AVAILABILITY_LINK.DEFAULT, - GETTING_STARTED_LINK[me?.ovhSubsidiary] || GETTING_STARTED_LINK.DEFAULT, - ]; - useEffect(() => { store.reset(); store.set.projectId(projectId); @@ -211,11 +227,41 @@ export default function CreatePage(): JSX.Element { store.set.gateways(subnetGateways || []); }, [subnetGateways]); - // TODO gateways messages + useEffect(() => { + if (store.region) { + const date = new Date(); + const maxRandomNumber = 9999; + + store.set.name( + `LB_${store.addon.code.toUpperCase()}_${ + store.region.name + }-${date.getDate()}${date.getMonth() + 1}-${(Math.floor( + Math.random() * maxRandomNumber, + ) * + new Date().getMilliseconds()) % + maxRandomNumber}`, + ); + } + }, [store.region]); + const create = async () => { + tracking.trackClick({ + name: LOAD_BALANCER_CREATION_TRACKING.SUBMIT, + type: 'action', + }); + + tracking.trackClick({ + name: `${LOAD_BALANCER_CREATION_TRACKING.CONFIRM}::${store.addon.code}::${store.region.name}`, + type: 'action', + }); + await store.create( flavor, () => { + tracking.trackPage({ + name: LOAD_BALANCER_CREATION_TRACKING.SUCCESS, + type: 'navigation', + }); addSuccess( {(_t) => _t('octavia_load_balancer_create_banner')} @@ -225,7 +271,10 @@ export default function CreatePage(): JSX.Element { navigate('..'); }, (error: ApiError) => { - console.log('error', error); + tracking.trackPage({ + name: LOAD_BALANCER_CREATION_TRACKING.ERROR, + type: 'navigation', + }); addError( {(_t) => ( @@ -249,6 +298,15 @@ export default function CreatePage(): JSX.Element { ); }; + const cancel = () => { + tracking.trackClick({ + name: LOAD_BALANCER_CREATION_TRACKING.CANCEL, + type: 'action', + }); + store.reset(); + navigate('..'); + }; + return ( <> { + trackStep(1); + store.check(StepsEnum.SIZE); store.lock(StepsEnum.SIZE); @@ -321,7 +381,7 @@ export default function CreatePage(): JSX.Element { > {tCreate('octavia_load_balancer_create_size_intro')}{' '} {tCreate('octavia_load_balancer_create_size_intro_link')} @@ -347,6 +407,8 @@ export default function CreatePage(): JSX.Element { order={2} next={{ action: () => { + trackStep(2); + store.check(StepsEnum.REGION); store.lock(StepsEnum.REGION); @@ -378,7 +440,10 @@ export default function CreatePage(): JSX.Element { > {tCreate('octavia_load_balancer_create_region_intro')}{' '} {tCreate('octavia_load_balancer_create_region_link')} @@ -442,6 +507,8 @@ export default function CreatePage(): JSX.Element { order={3} next={{ action: () => { + trackStep(3); + store.check(StepsEnum.PUBLIC_IP); store.lock(StepsEnum.PUBLIC_IP); @@ -497,6 +564,7 @@ export default function CreatePage(): JSX.Element { store.set.publicIp(targetIp); }} inline + {...(floatingIpsList.length === 0 ? { disabled: true } : {})} > { + trackStep(4); + store.check(StepsEnum.PRIVATE_NETWORK); store.lock(StepsEnum.PRIVATE_NETWORK); @@ -621,9 +691,6 @@ export default function CreatePage(): JSX.Element { > {tCreate('octavia_load_balancer_create_private_network_field')} - { - // TODO disable selects if no data - } { + trackStep(5); + store.check(StepsEnum.INSTANCE); store.lock(StepsEnum.INSTANCE); @@ -805,6 +875,22 @@ export default function CreatePage(): JSX.Element { }, label: tCommon('common_stepper_modify_this_step'), }} + skip={{ + action: () => { + tracking.trackClick({ + name: LOAD_BALANCER_CREATION_TRACKING.SKIP_STEP_5, + type: 'action', + }); + store.set.listeners([]); + + store.check(StepsEnum.INSTANCE); + store.lock(StepsEnum.INSTANCE); + + store.open(StepsEnum.NAME); + }, + label: tCommon('common_stepper_skip_this_step'), + hint: `${tCommon('common_stepper_optional_label')}`, + }} > {(_t) => ( @@ -815,10 +901,14 @@ export default function CreatePage(): JSX.Element { className="mb-4" > @@ -948,6 +1038,7 @@ export default function CreatePage(): JSX.Element { inline variant={ODS_BUTTON_VARIANT.ghost} color={ODS_THEME_COLOR_INTENT.info} + onClick={cancel} > {tCommon('common_cancel')} diff --git a/yarn.lock b/yarn.lock index 4c210de42144..4e76a371e4f4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -28056,6 +28056,11 @@ uuid@9.0.1, uuid@^9.0.0, uuid@^9.0.1: resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30" integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA== +uuid@^10.0.0: + version "10.0.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-10.0.0.tgz#5a95aa454e6e002725c79055fd42aaba30ca6294" + integrity sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ== + uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"