diff --git a/packages/manager/.changeset/pr-13223-changed-1766491572015.md b/packages/manager/.changeset/pr-13223-changed-1766491572015.md new file mode 100644 index 00000000000..3b5fb24d878 --- /dev/null +++ b/packages/manager/.changeset/pr-13223-changed-1766491572015.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Changed +--- + +UX/UI changes in Linode Create flow - Networking ([#13223](https://github.com/linode/manager/pull/13223)) diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-with-vlan.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-with-vlan.spec.ts index fd96e194f24..1b4a1c62f3e 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-with-vlan.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-with-vlan.spec.ts @@ -319,9 +319,14 @@ describe('Create Linode with VLANs (Linode Interfaces)', () => { // Confirm the Linode Interfaces section is shown. assertNewLinodeInterfacesIsAvailable(); - // Open VLAN accordion and select existing VLAN. - cy.get('[data-qa-select-card-heading="VLAN"]').should('be.visible').click(); - cy.findByLabelText('VLAN').should('be.enabled').type(mockVlan.label); + // select existing VLAN. + linodeCreatePage.selectInterface('vlan'); + // Confirm that mocked VLAN is shown in the Autocomplete, and then select it. + cy.get('[data-qa-autocomplete="VLAN"]').within(() => { + cy.findByLabelText('VLAN').should('be.visible'); + cy.get('[data-testid="textfield-input"]').click(); + cy.focused().type(`${mockVlan.label}`); + }); ui.autocompletePopper .findByTitle(mockVlan.label) .should('be.visible') @@ -399,10 +404,14 @@ describe('Create Linode with VLANs (Linode Interfaces)', () => { linodeCreatePage.selectLinodeInterfacesType(); // Select VLAN card - linodeCreatePage.selectInterfaceCard('VLAN'); + linodeCreatePage.selectInterface('vlan'); - // Open VLAN accordion and select existing VLAN. - cy.findByLabelText('VLAN').should('be.enabled').type(mockVlan.label); + // select existing VLAN. + cy.get('[data-qa-autocomplete="VLAN"]').within(() => { + cy.findByLabelText('VLAN').should('be.visible'); + cy.get('[data-testid="textfield-input"]').click(); + cy.focused().type(`${mockVlan.label}`); + }); ui.autocompletePopper .findByTitle(mockVlan.label) .should('be.visible') @@ -477,10 +486,14 @@ describe('Create Linode with VLANs (Linode Interfaces)', () => { assertNewLinodeInterfacesIsAvailable(); // Select VLAN card - linodeCreatePage.selectInterfaceCard('VLAN'); + linodeCreatePage.selectInterface('vlan'); - // Open VLAN accordion and specify new VLAN. - cy.findByLabelText('VLAN').should('be.enabled').type(mockVlan.label); + // select new VLAN. + cy.get('[data-qa-autocomplete="VLAN"]').within(() => { + cy.findByLabelText('VLAN').should('be.visible'); + cy.get('[data-testid="textfield-input"]').click(); + cy.focused().type(`${mockVlan.label}`); + }); ui.autocompletePopper .findByTitle(`Create "${mockVlan.label}"`) .should('be.visible') @@ -558,10 +571,14 @@ describe('Create Linode with VLANs (Linode Interfaces)', () => { linodeCreatePage.selectLinodeInterfacesType(); // Select VLAN card - linodeCreatePage.selectInterfaceCard('VLAN'); + linodeCreatePage.selectInterface('vlan'); - // Open VLAN accordion and specify new VLAN. - cy.findByLabelText('VLAN').should('be.enabled').type(mockVlan.label); + // select new VLAN. + cy.get('[data-qa-autocomplete="VLAN"]').within(() => { + cy.findByLabelText('VLAN').should('be.visible'); + cy.get('[data-testid="textfield-input"]').click(); + cy.focused().type(`${mockVlan.label}`); + }); ui.autocompletePopper .findByTitle(`Create "${mockVlan.label}"`) .should('be.visible') @@ -620,9 +637,9 @@ describe('Create Linode with VLANs (Linode Interfaces)', () => { assertNewLinodeInterfacesIsAvailable(); // Select VLAN card - linodeCreatePage.selectInterfaceCard('VLAN'); + linodeCreatePage.selectInterface('vlan'); - // Expand VLAN accordion, confirm VLAN availability notice is displayed and + // confirm VLAN availability notice is displayed and // that VLAN fields are disabled while no region is selected. cy.findByText('VLAN is not available in the selected region.', { exact: false, diff --git a/packages/manager/cypress/e2e/core/linodes/create-linode-with-vpc.spec.ts b/packages/manager/cypress/e2e/core/linodes/create-linode-with-vpc.spec.ts index ae8f63ed9b4..ea1688c42d8 100644 --- a/packages/manager/cypress/e2e/core/linodes/create-linode-with-vpc.spec.ts +++ b/packages/manager/cypress/e2e/core/linodes/create-linode-with-vpc.spec.ts @@ -476,8 +476,8 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { // Confirm the Linode Interfaces section is shown. assertNewLinodeInterfacesIsAvailable(); - // Select VPC card - linodeCreatePage.selectInterfaceCard('VPC'); + // Select VPC + linodeCreatePage.selectInterface('vpc'); // Confirm that mocked VPC is shown in the Autocomplete, and then select it. cy.get('[data-qa-autocomplete="VPC"]').within(() => { @@ -615,8 +615,8 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { // Switch to Linode Interfaces linodeCreatePage.selectLinodeInterfacesType(); - // Select VPC card - linodeCreatePage.selectInterfaceCard('VPC'); + // Select VPC option + linodeCreatePage.selectInterface('vpc'); // Confirm that mocked VPC is shown in the Autocomplete, and then select it. cy.get('[data-qa-autocomplete="VPC"]').within(() => { @@ -751,7 +751,7 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { assertNewLinodeInterfacesIsAvailable(); // Select VPC card - linodeCreatePage.selectInterfaceCard('VPC'); + linodeCreatePage.selectInterface('vpc'); cy.findByText('Create VPC').should('be.visible').click(); @@ -937,7 +937,7 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { linodeCreatePage.selectLinodeInterfacesType(); // Select VPC card - linodeCreatePage.selectInterfaceCard('VPC'); + linodeCreatePage.selectInterface('vpc'); cy.findByText('Create VPC').should('be.visible').click(); @@ -1068,7 +1068,7 @@ describe('Create Linode with VPCs (Linode Interfaces)', () => { assertNewLinodeInterfacesIsAvailable(); // Select VPC card. - linodeCreatePage.selectInterfaceCard('VPC'); + linodeCreatePage.selectInterface('vpc'); // Confirm that VPC selection is disabled. cy.get('[data-qa-autocomplete="VPC"]').within(() => { diff --git a/packages/manager/cypress/support/constants/linode-interfaces.ts b/packages/manager/cypress/support/constants/linode-interfaces.ts index b734307f030..d78f38c6ed9 100644 --- a/packages/manager/cypress/support/constants/linode-interfaces.ts +++ b/packages/manager/cypress/support/constants/linode-interfaces.ts @@ -44,7 +44,7 @@ export const linodeInterfacesLabelText = 'Linode Interfaces'; export const betaLabelText = 'beta'; export const linodeInterfacesDescriptionText1 = - 'Linode Interfaces are the preferred option for VPCs and are managed directly through a Linode’s Network settings.'; + "Managed directly through a Linode's Network settings. This is the recommended option."; export const linodeInterfacesDescriptionText2 = 'Cloud Firewalls are assigned to individual VPC and public interfaces.'; @@ -53,7 +53,7 @@ export const legacyInterfacesLabelText = 'Configuration Profile Interfaces (Legacy)'; export const legacyInterfacesDescriptionText1 = - 'Interfaces in the Configuration Profile are part of a Linode’s configuration.'; + 'Interfaces are part of the Linode’s Configuration Profile.'; export const legacyInterfacesDescriptionText2 = 'Cloud Firewalls are applied at the Linode level and automatically cover all non-VLAN interfaces in the Configuration Profile.'; diff --git a/packages/manager/cypress/support/ui/pages/linode-create-page.ts b/packages/manager/cypress/support/ui/pages/linode-create-page.ts index 2d4f986e197..c272642c6e4 100644 --- a/packages/manager/cypress/support/ui/pages/linode-create-page.ts +++ b/packages/manager/cypress/support/ui/pages/linode-create-page.ts @@ -130,7 +130,7 @@ export const linodeCreatePage = { * Select the Linode Interfaces Type. */ selectLinodeInterfacesType: () => { - cy.findByText('Linode Interfaces').click(); + cy.get('[data-qa-interfaces-option="linode"]').click(); }, /** @@ -141,13 +141,11 @@ export const linodeCreatePage = { }, /** - * Select the interfaces' card. + * Select the interfaces' type. * - * @param title - Interfaces' card title to select. + * @param type - Interfaces' type title to select. */ - selectInterfaceCard: (title: string) => { - cy.get(`[data-qa-select-card-heading="${title}"]`) - .should('be.visible') - .click(); + selectInterface: (type: 'public' | 'vlan' | 'vpc') => { + cy.get(`[data-qa-interface-type-option="${type}"]`).click(); }, }; diff --git a/packages/manager/cypress/support/util/linodes.ts b/packages/manager/cypress/support/util/linodes.ts index 98ee8d6af74..ba990a3c94d 100644 --- a/packages/manager/cypress/support/util/linodes.ts +++ b/packages/manager/cypress/support/util/linodes.ts @@ -5,13 +5,7 @@ import { findOrCreateDependencyVlan } from 'support/api/vlans'; import { pageSize } from 'support/constants/api'; import { dryRunButtonText, - legacyInterfacesDescriptionText1, - legacyInterfacesDescriptionText2, legacyInterfacesLabelText, - linodeInterfacesDescriptionText1, - linodeInterfacesDescriptionText2, - linodeInterfacesLabelText, - networkConnectionDescriptionText, networkConnectionSectionText, networkInterfaceTypeSectionText, promptDialogDescription1, @@ -330,14 +324,9 @@ export const assertNewLinodeInterfacesIsAvailable = ( ): void => { const expectedBehavior = linodeInterfacesEnabled ? 'be.visible' : 'not.exist'; cy.findByText(networkInterfaceTypeSectionText).should(expectedBehavior); - cy.findByText(linodeInterfacesLabelText).should(expectedBehavior); - cy.findByText(linodeInterfacesDescriptionText1).should(expectedBehavior); - cy.findByText(linodeInterfacesDescriptionText2).should(expectedBehavior); + cy.get('[data-qa-interfaces-option="linode"]').should(expectedBehavior); cy.findByText(legacyInterfacesLabelText).should(expectedBehavior); - cy.findByText(legacyInterfacesDescriptionText1).should(expectedBehavior); - cy.findByText(legacyInterfacesDescriptionText2).should(expectedBehavior); cy.findByText(networkConnectionSectionText).should(expectedBehavior); - cy.findByText(networkConnectionDescriptionText).should(expectedBehavior); }; /** diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.test.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.test.tsx index 53ba9c1c1b0..ae7dbecf155 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.test.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.test.tsx @@ -33,7 +33,7 @@ describe('InterfaceGeneration', () => { await userEvent.hover(getByText('Network Interface Type')); await findByText( - 'You account administrator has enforced that all new Linodes are created with Linode interfaces.' + 'Your account administrator has enforced that all new Linodes are created with Linode interfaces.' ); for (const radio of getAllByRole('radio')) { @@ -66,7 +66,7 @@ describe('InterfaceGeneration', () => { await userEvent.hover(getByText('Network Interface Type')); await findByText( - 'You account administrator has enforced that all new Linodes are created with legacy configuration interfaces.' + 'Your account administrator has enforced that all new Linodes are created with legacy configuration interfaces.' ); const radios = getAllByRole('radio'); diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx index 31c393efb2d..bd4cf8cf802 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.tsx @@ -6,6 +6,7 @@ import { Radio, RadioGroup, Stack, + TooltipIcon, Typography, } from '@linode/ui'; import React from 'react'; @@ -22,9 +23,9 @@ const disabledReasonMap: Partial< Record > = { legacy_config_only: - 'You account administrator has enforced that all new Linodes are created with legacy configuration interfaces.', + 'Your account administrator has enforced that all new Linodes are created with legacy configuration interfaces.', linode_only: - 'You account administrator has enforced that all new Linodes are created with Linode interfaces.', + 'Your account administrator has enforced that all new Linodes are created with Linode interfaces.', }; export const InterfaceGeneration = () => { @@ -56,7 +57,10 @@ export const InterfaceGeneration = () => { defaultExpanded={!disabled} name="Network Interface Type" > - + { > } + data-qa-interfaces-option="linode" label={ @@ -72,15 +77,21 @@ export const InterfaceGeneration = () => { Linode Interfaces + + Managed directly through a Linode's Network + settings. This is the recommended option. +
+
+ Cloud Firewalls are assigned to individual VPC and + public interfaces. + + } + />
- - Linode Interfaces are the preferred option for VPCs and are - managed directly through a Linode’s Network settings. - - - Cloud Firewalls are assigned to individual VPC and public - interfaces. -
} sx={{ alignItems: 'flex-start' }} @@ -88,20 +99,27 @@ export const InterfaceGeneration = () => { /> } + data-qa-interfaces-option="legacy_config" label={ - + ({ font: theme.font.bold })}> Configuration Profile Interfaces (Legacy) - - Interfaces in the Configuration Profile are part of a - Linode’s configuration. - - - Cloud Firewalls are applied at the Linode level and - automatically cover all non-VLAN interfaces in the - Configuration Profile. - + + Interfaces are part of the Linode's Configuration + Profile. +
+
+ Cloud Firewalls are applied at the Linode level and + automatically cover all non-VLAN interfaces in the + Configuration Profile. + + } + />
} sx={{ alignItems: 'flex-start' }} diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceType.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceType.tsx index 62dc33ee27b..fbf5da03b00 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceType.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceType.tsx @@ -7,13 +7,13 @@ import { TooltipIcon, Typography, } from '@linode/ui'; -import { Grid } from '@mui/material'; +import { FormControlLabel, Stack } from '@mui/material'; import { useSnackbar } from 'notistack'; +import type { ChangeEvent } from 'react'; import React from 'react'; import { useController, useFormContext, useWatch } from 'react-hook-form'; import { FormLabel } from 'src/components/FormLabel'; -import { SelectionCard } from 'src/components/SelectionCard/SelectionCard'; import { useGetLinodeCreateType } from '../Tabs/utils/useGetLinodeCreateType'; import { getDefaultFirewallForInterfacePurpose } from './utilities'; @@ -36,7 +36,7 @@ const interfaceTypes = [ label: 'VPC', purpose: 'vpc', description: - 'Connects your Linode to a private, Layer 3–isolated network, enabling secure communication with other Linodes in the same VPC.', + 'Connects your Linode to a private, Layer 3 isolated network, allowing secure communication with other Linodes in the same VPC.', }, { label: 'VLAN', @@ -122,48 +122,34 @@ export const InterfaceType = ({ index }: Props) => { /> )} - - The default interface used by this Linode to route network traffic. - Additional interfaces can be added after the Linode is created. - onChange(value)} + value={field.value} > - - {interfaceTypes.map((interfaceType) => ( - onChange(interfaceType.purpose)} - renderIcon={() => ( - - )} - renderVariant={() => ( + {interfaceTypes.map((interfaceType) => ( + } + data-qa-interface-type-option={interfaceType.purpose} + disabled={disabled} + key={interfaceType.purpose} + label={ + + ({ font: theme.font.bold })}> + {interfaceType.label} + - )} - subheadings={[]} - sxCardBaseIcon={{ svg: { fontSize: '20px' } }} - /> - ))} - +
+ } + sx={{ alignItems: 'flex-start' }} + value={disabled ? false : interfaceType.purpose} + /> + ))}
); diff --git a/packages/manager/src/features/Linodes/LinodeCreate/Networking/VLAN.tsx b/packages/manager/src/features/Linodes/LinodeCreate/Networking/VLAN.tsx index d64ceaf6110..54810126cf4 100644 --- a/packages/manager/src/features/Linodes/LinodeCreate/Networking/VLAN.tsx +++ b/packages/manager/src/features/Linodes/LinodeCreate/Networking/VLAN.tsx @@ -27,7 +27,7 @@ export const VLAN = ({ index }: Props) => { selectedRegion?.capabilities.includes('Vlans') ?? false; return ( - + {selectedRegion && !regionSupportsVLANs && } { Boolean(selectedSubnet?.ipv6?.length && selectedSubnet?.ipv6?.length > 0); return ( - + {selectedRegion && !regionSupportsVPCs && } { )} /> {showIPv6Fields && ( - ( - - )} - /> + + ( + + )} + /> + )}