Skip to content

Commit

Permalink
feat: [M3-8326], [M3-8055] - Create Linode - Create Using Command Lin…
Browse files Browse the repository at this point in the history
…e A/B Test (#10906)

* unit test coverage for HostNameTableCell

* Revert "unit test coverage for HostNameTableCell"

This reverts commit b274baf.

* Create feature flag for DX tools A/B testing

* Add data attributes for DX tools for tracking

* Update Actions.test.tsx

* Test flag

* Add classname

* Add event emitters to the LD from DX tools

* update with correct keys

* Destructure tabs

* code cleanup

* Add event emitter to Integration and SDK tab resources.

* Add go and python code snippets

* Add Linode CLI Tab Selection

* Remove old ab test code

* Cleanup conditionals

* Add changeset

* Revert "Merge branch 'linode:develop' into develop"

This reverts commit bfed239, reversing
changes made to 342fd96.

* Reapply "Merge branch 'linode:develop' into develop"

This reverts commit 9e54b55.

---------

Co-authored-by: Jaalah Ramos <jaalah.ramos@gmail.com>
  • Loading branch information
2 people authored and hkhalil-akamai committed Sep 13, 2024
1 parent ad98262 commit 33eb344
Show file tree
Hide file tree
Showing 20 changed files with 247 additions and 32 deletions.
1 change: 1 addition & 0 deletions packages/manager/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- DisplayPrice story in Storybook ([#10904](https://github.com/linode/manager/pull/10904))
- CheckoutSummary story in Storybook ([#10905](https://github.com/linode/manager/pull/10905))
- CopyableTextField story and cleaned up components ([#10912](https://github.com/linode/manager/pull/10912))
- Tracking metrics for LD DX Tools AB Test ([#10906](https://github.com/linode/manager/pull/10906))

### Changed:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,18 @@ import { getLinkOnClick } from 'src/utilities/emptyStateLandingUtils';
import type { ResourcesLinks } from './ResourcesLinksTypes';

export const ResourceLinks = (props: ResourcesLinks) => {
const { linkAnalyticsEvent, links } = props;
const { linkAnalyticsEvent, links, onClick } = props;

return (
<List>
{links.map((linkData) => (
<ListItem key={linkData.to}>
<Link
onClick={() => {
getLinkOnClick(linkAnalyticsEvent, linkData.text);
onClick?.();
}}
external={linkData.external}
onClick={getLinkOnClick(linkAnalyticsEvent, linkData.text)}
to={linkData.to}
>
{linkData.text}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface ResourcesHeaders {
export interface ResourcesLinks {
linkAnalyticsEvent: linkAnalyticsEvent;
links: ResourcesLink[];
onClick?: () => void;
}

export interface ResourcesLinkSection {
Expand Down
31 changes: 31 additions & 0 deletions packages/manager/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -294,3 +294,34 @@ export const FIREWALL_GET_STARTED_LINK =
'https://www.linode.com/docs/products/networking/cloud-firewall/get-started/';
export const FIREWALL_LIMITS_CONSIDERATIONS_LINK =
'https://www.linode.com/docs/products/networking/cloud-firewall/#limits-and-considerations';

// A/B Testing LD metrics keys for DX Tools
export const LD_DX_TOOLS_METRICS_KEYS = {
CURL_CODE_SNIPPET: 'A/B Test: Step 2 : cURL copy code snippet (copy icon)',
CURL_RESOURCE_LINKS: 'A/B Test: Step 2 : DX Tools cURL resources links',
CURL_TAB_SELECTION: 'A/B Test: Step 2 : DX Tools cURL tab selection',
INTEGRATION_ANSIBLE_CODE_SNIPPET:
'A/B Test: Step 2 : Integrations: Ansible copy code snippet (copy icon)',
INTEGRATION_ANSIBLE_RESOURCE_LINKS:
'a-b-test-step-2-dx-tools-integrations-ansible-resources-links',
INTEGRATION_TAB_SELECTION:
'A/B Test: Step 2 : DX Tools Integrations tab selection',
INTEGRATION_TERRAFORM_CODE_SNIPPET:
'A/B Test: Step 2 : Integrations: Terraform copy code snippet (copy icon)',
INTEGRATION_TERRAFORM_RESOURCE_LINKS:
'A/B Test: Step 2 : DX Tools integrations terraform resources links',
LINODE_CLI_CODE_SNIPPET:
'A/B Test: Step 2 : Linode CLI Tab selection and copy code snippet (copy icon)',
LINODE_CLI_RESOURCE_LINKS:
'A/B Test: Step 2 : DX Tools Linode CLI resources links',
LINODE_CLI_TAB_SELECTION: 'A/B Test: Step 2 : Linode CLI Tab Selection',
OPEN_MODAL: 'A/B Test: Step 1 : DX Tools Open Modal',
SDK_GO_CODE_SNIPPET:
'A/B Test: Step 2 : SDK: GO copy code snippet (copy icon)',
SDK_GO_RESOURCE_LINKS: 'A/B Test: Step 2 : DX Tools SDK GO resources links',
SDK_PYTHON_CODE_SNIPPET:
'A/B Test: Step 2 : SDK: Python copy code snippet (copy icon)',
SDK_PYTHON_RESOURCE_LINKS:
'A/B Test: Step 2 : DX Tools SDK Python resources links',
SDK_TAB_SELECTION: 'A/B Test: Step 2 : DX Tools SDK tab selection',
};
1 change: 1 addition & 0 deletions packages/manager/src/dev-tools/FeatureFlagTool.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const options: { flag: keyof Flags; label: string }[] = [
{ flag: 'dbaasV2', label: 'Databases V2 Beta' },
{ flag: 'databaseResize', label: 'Database Resize' },
{ flag: 'apicliDxToolsAdditions', label: 'APICLI DX Tools Additions' },
{ flag: 'apicliButtonCopy', label: 'APICLI Button Copy' },
];

const renderFlagItems = (
Expand Down
2 changes: 2 additions & 0 deletions packages/manager/src/featureFlags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,10 +81,12 @@ interface DesignUpdatesBannerFlag extends BaseFeatureFlag {
}

export interface Flags {
testdxtoolabexperiment: string;
aclp: AclpFlag;
aclpReadEndpoint: string;
aclpResourceTypeMap: CloudPulseResourceTypeMapFlag[];
apiMaintenance: APIMaintenance;
apicliButtonCopy: string;
apicliDxToolsAdditions: boolean;
blockStorageEncryption: boolean;
cloudManagerDesignUpdatesBanner: DesignUpdatesBannerFlag;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('Actions', () => {
component: <Actions />,
});

const button = getByText('Create using command line').closest('button');
const button = getByText('Create Using Command Line').closest('button');

expect(button).toBeVisible();
expect(button).toBeEnabled();
Expand Down
15 changes: 13 additions & 2 deletions packages/manager/src/features/Linodes/LinodeCreatev2/Actions.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { useLDClient } from 'launchdarkly-react-client-sdk';
import React, { useState } from 'react';
import { useFormContext } from 'react-hook-form';

import { Box } from 'src/components/Box';
import { Button } from 'src/components/Button/Button';
import { LD_DX_TOOLS_METRICS_KEYS } from 'src/constants';
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';
Expand All @@ -17,10 +20,14 @@ import {
import type { LinodeCreateFormValues } from './utilities';

export const Actions = () => {
const flags = useFlags();
const ldClient = useLDClient();
const { params } = useLinodeCreateQueryParams();

const [isAPIAwarenessModalOpen, setIsAPIAwarenessModalOpen] = useState(false);

const apicliButtonCopy = flags?.testdxtoolabexperiment;

const {
formState,
getValues,
Expand All @@ -39,11 +46,15 @@ export const Actions = () => {
sendLinodeCreateFormInputEvent({
createType: params.type ?? 'OS',
interaction: 'click',
label: 'Create Using Command Line',
label: apicliButtonCopy ?? 'Create Using Command Line',
});
if (await trigger()) {
// If validation is successful, we open the dialog.
setIsAPIAwarenessModalOpen(true);

ldClient?.track(LD_DX_TOOLS_METRICS_KEYS.OPEN_MODAL, {
variation: apicliButtonCopy,
});
} else {
scrollErrorIntoView(undefined, { behavior: 'smooth' });
}
Expand All @@ -52,7 +63,7 @@ export const Actions = () => {
return (
<Box sx={{ display: 'flex', gap: 1, justifyContent: 'flex-end' }}>
<Button buttonType="outlined" onClick={onOpenAPIAwareness}>
Create using command line
{apicliButtonCopy ?? 'Create Using Command Line'}
</Button>
<Button
buttonType="primary"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { useLDClient } from 'launchdarkly-react-client-sdk';
import React from 'react';

import { ResourceLinks } from 'src/components/EmptyLandingPageResources/ResourcesLinks';
import { Typography } from 'src/components/Typography';
import { LD_DX_TOOLS_METRICS_KEYS } from 'src/constants';
import { useFlags } from 'src/hooks/useFlags';

import type { ResourcesLinks } from 'src/components/EmptyLandingPageResources/ResourcesLinksTypes';

Expand Down Expand Up @@ -37,6 +40,19 @@ export const gettingStartedGuides: ResourcesLinks['links'] = [
];

export const AnsibleIntegrationResources = () => {
const ldClient = useLDClient();
const flags = useFlags();

const apicliButtonCopy = flags?.testdxtoolabexperiment;

const handleClick = () => {
ldClient?.track(
LD_DX_TOOLS_METRICS_KEYS.INTEGRATION_ANSIBLE_RESOURCE_LINKS,
{
variation: apicliButtonCopy,
}
);
};
return (
<>
<Typography sx={(theme) => ({ mt: theme.spacing(2) })} variant="h3">
Expand All @@ -45,6 +61,7 @@ export const AnsibleIntegrationResources = () => {
<ResourceLinks
linkAnalyticsEvent={linkAnalyticsEvent}
links={gettingStartedGuides}
onClick={handleClick}
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { styled } from '@mui/material/styles';
import { useLDClient } from 'launchdarkly-react-client-sdk';
import React, { useEffect } from 'react';
import { useHistory } from 'react-router-dom';

Expand All @@ -11,6 +12,7 @@ import { TabList } from 'src/components/Tabs/TabList';
import { TabPanels } from 'src/components/Tabs/TabPanels';
import { Tabs } from 'src/components/Tabs/Tabs';
import { Typography } from 'src/components/Typography';
import { LD_DX_TOOLS_METRICS_KEYS } from 'src/constants';
import { useFlags } from 'src/hooks/useFlags';
import { useInProgressEvents } from 'src/queries/events/events';
import { sendApiAwarenessClickEvent } from 'src/utilities/analytics/customEventAnalytics';
Expand Down Expand Up @@ -58,6 +60,7 @@ export const ApiAwarenessModal = (props: ApiAwarenessModalProps) => {
const { isOpen, onClose, payLoad } = props;

const flags = useFlags();
const ldClient = useLDClient();
const history = useHistory();
const { data: events } = useInProgressEvents();

Expand All @@ -71,13 +74,33 @@ export const ApiAwarenessModal = (props: ApiAwarenessModalProps) => {
const isLinodeCreated = linodeCreationEvent !== undefined;

const isDxAdditionsFeatureEnabled = flags?.apicliDxToolsAdditions;
const apicliButtonCopy = flags?.testdxtoolabexperiment;

const tabs = isDxAdditionsFeatureEnabled
? [baseTabs[1], baseTabs[0], ...additionalTabs]
: baseTabs;

const handleTabChange = (index: number) => {
sendApiAwarenessClickEvent(`${tabs[index].type} Tab`, tabs[index].type);
const { title, type } = tabs[index];

sendApiAwarenessClickEvent(`${type} Tab`, type);

const trackingKey =
type === 'INTEGRATIONS' && title !== "SDK's"
? LD_DX_TOOLS_METRICS_KEYS.INTEGRATION_TAB_SELECTION
: type === 'API'
? LD_DX_TOOLS_METRICS_KEYS.CURL_TAB_SELECTION
: title === "SDK's"
? LD_DX_TOOLS_METRICS_KEYS.SDK_TAB_SELECTION
: title === 'Linode CLI'
? LD_DX_TOOLS_METRICS_KEYS.LINODE_CLI_TAB_SELECTION
: undefined;

if (trackingKey) {
ldClient?.track(trackingKey, {
variation: apicliButtonCopy,
});
}
};

useEffect(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { useTheme } from '@mui/material/styles';
import { useLDClient } from 'launchdarkly-react-client-sdk';
import React, { useMemo } from 'react';

import { Link } from 'src/components/Link';
import { SafeTabPanel } from 'src/components/Tabs/SafeTabPanel';
import { Typography } from 'src/components/Typography';
import { LD_DX_TOOLS_METRICS_KEYS } from 'src/constants';
import { useFlags } from 'src/hooks/useFlags';
import { sendApiAwarenessClickEvent } from 'src/utilities/analytics/customEventAnalytics';
import { generateCurlCommand } from 'src/utilities/codesnippets/generate-cURL';

Expand All @@ -18,48 +21,67 @@ export interface CurlTabPanelProps {
}

export const CurlTabPanel = ({ index, payLoad, title }: CurlTabPanelProps) => {
const flags = useFlags();
const ldClient = useLDClient();
const theme = useTheme();
const curlCommand = useMemo(
() => generateCurlCommand(payLoad, '/linode/instances'),
[payLoad]
);
const apicliButtonCopy = flags?.testdxtoolabexperiment;
return (
<SafeTabPanel index={index}>
<Typography sx={{ marginTop: theme.spacing(2) }} variant="body1">
Most Linode API requests need to be authenticated with a valid{' '}
<Link
onClick={() =>
sendApiAwarenessClickEvent('link', 'personal access token')
}
onClick={() => {
sendApiAwarenessClickEvent('link', 'personal access token');
ldClient?.track(LD_DX_TOOLS_METRICS_KEYS.CURL_RESOURCE_LINKS, {
variation: apicliButtonCopy,
});
}}
to="/profile/tokens"
>
personal access token
</Link>
. The command below assumes that your personal access token has been
stored within the TOKEN shell variable. For more information, see{' '}
<Link
onClick={() =>
onClick={() => {
sendApiAwarenessClickEvent(
'link',
'Get Started with the Linode API'
)
}
);

ldClient?.track(LD_DX_TOOLS_METRICS_KEYS.CURL_RESOURCE_LINKS, {
variation: apicliButtonCopy,
});
}}
to="https://www.linode.com/docs/products/tools/api/get-started/"
>
Get Started with the Linode API
</Link>{' '}
and{' '}
<Link
onClick={() =>
sendApiAwarenessClickEvent('link', 'Linode API Guides')
}
onClick={() => {
sendApiAwarenessClickEvent('link', 'Linode API Guides');

ldClient?.track(LD_DX_TOOLS_METRICS_KEYS.CURL_RESOURCE_LINKS, {
variation: apicliButtonCopy,
});
}}
to="https://www.linode.com/docs/products/tools/api/guides/"
>
Linode API Guides
</Link>
.
</Typography>
<CodeBlock command={curlCommand} commandType={title} language={'bash'} />
<CodeBlock
command={curlCommand}
commandType={title}
language={'bash'}
ldTrackingKey={LD_DX_TOOLS_METRICS_KEYS.CURL_CODE_SNIPPET}
/>
</SafeTabPanel>
);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { useLDClient } from 'launchdarkly-react-client-sdk';
import React from 'react';

import { ResourceLinks } from 'src/components/EmptyLandingPageResources/ResourcesLinks';
import { Typography } from 'src/components/Typography';
import { LD_DX_TOOLS_METRICS_KEYS } from 'src/constants';
import { useFlags } from 'src/hooks/useFlags';

import type { ResourcesLinks } from 'src/components/EmptyLandingPageResources/ResourcesLinksTypes';

Expand All @@ -22,6 +25,16 @@ export const gettingStartedGuides: ResourcesLinks['links'] = [
];

export const GoSDKResources = () => {
const ldClient = useLDClient();
const flags = useFlags();

const apicliButtonCopy = flags?.testdxtoolabexperiment;

const handleClick = () => {
ldClient?.track(LD_DX_TOOLS_METRICS_KEYS.SDK_GO_RESOURCE_LINKS, {
variation: apicliButtonCopy,
});
};
return (
<>
<Typography sx={(theme) => ({ mt: theme.spacing(2) })} variant="h3">
Expand All @@ -30,6 +43,7 @@ export const GoSDKResources = () => {
<ResourceLinks
linkAnalyticsEvent={linkAnalyticsEvent}
links={gettingStartedGuides}
onClick={handleClick}
/>
</>
);
Expand Down
Loading

0 comments on commit 33eb344

Please sign in to comment.