Skip to content

Commit 810724f

Browse files
change: [UIE-10012] - Implement UX and user feedback for linode interfaces feature in Create linode page
1 parent 6dbabd7 commit 810724f

File tree

9 files changed

+356
-125
lines changed

9 files changed

+356
-125
lines changed

packages/manager/src/features/Linodes/LinodeCreate/Addons/Backups.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ export const Backups = () => {
108108
<React.Fragment>
109109
Three backup slots are executed and rotated automatically: a
110110
daily backup, a 2-7 day old backup, and an 8-14 day old backup.
111-
Plans are priced according to the Linode plan selected above.
111+
Pricing is based on the selected Linode plan.
112112
</React.Fragment>
113113
)}
114114
</Typography>

packages/manager/src/features/Linodes/LinodeCreate/Addons/PrivateIP.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,8 +63,9 @@ export const PrivateIP = () => {
6363
<Stack alignItems="center" direction="row" spacing={1}>
6464
<NewFeatureChip />
6565
<Typography>
66-
You can use VPC for private networking instead. NodeBalancers
67-
now connect to backend nodes without a private IPv4 address.
66+
You can now establish network isolation and connections to
67+
NodeBalancer backends through VPC. We recommend using VPC
68+
instead of Private IPs.
6869
</Typography>
6970
</Stack>
7071
</Notice>

packages/manager/src/features/Linodes/LinodeCreate/Networking/InterfaceGeneration.test.tsx

Lines changed: 137 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,34 @@ import { renderWithThemeAndHookFormContext } from 'src/utilities/testHelpers';
77

88
import { InterfaceGeneration } from './InterfaceGeneration';
99

10+
const getAccountSettingsAPI = '*/v4*/account/settings';
11+
1012
describe('InterfaceGeneration', () => {
1113
it('disables the radios if the account setting enforces linode_only interfaces', async () => {
1214
const accountSettings = accountSettingsFactory.build({
1315
interfaces_for_new_linodes: 'linode_only',
1416
});
1517

1618
server.use(
17-
http.get('*/v4*/account/settings', () => {
19+
http.get(getAccountSettingsAPI, () => {
1820
return HttpResponse.json(accountSettings);
1921
})
2022
);
2123

22-
const { findByText, getAllByRole, getByText } =
23-
renderWithThemeAndHookFormContext({
24-
component: <InterfaceGeneration />,
25-
});
26-
27-
const button = getByText('Network Interface Type');
28-
29-
// Expand the "Show More"
30-
await userEvent.click(button);
24+
const { getAllByRole, findByRole } = renderWithThemeAndHookFormContext({
25+
component: <InterfaceGeneration />,
26+
});
3127

32-
// Hover to check for the tooltip
33-
await userEvent.hover(getByText('Network Interface Type'));
28+
// Wait for the tooltip icon to appear (indicating the disabled state)
29+
await findByRole('button', {
30+
name: 'Your account administrator has enforced that all new Linodes are created with Linode interfaces.',
31+
});
3432

35-
await findByText(
36-
'Your account administrator has enforced that all new Linodes are created with Linode interfaces.'
37-
);
33+
// Verify both radio buttons are disabled
34+
const radios = getAllByRole('radio');
35+
expect(radios).toHaveLength(2);
3836

39-
for (const radio of getAllByRole('radio')) {
37+
for (const radio of radios) {
4038
expect(radio).toBeDisabled();
4139
}
4240
});
@@ -47,34 +45,143 @@ describe('InterfaceGeneration', () => {
4745
});
4846

4947
server.use(
50-
http.get('*/v4*/account/settings', () => {
48+
http.get(getAccountSettingsAPI, () => {
5149
return HttpResponse.json(accountSettings);
5250
})
5351
);
5452

55-
const { findByText, getAllByRole, getByText } =
56-
renderWithThemeAndHookFormContext({
57-
component: <InterfaceGeneration />,
58-
});
53+
const { getAllByRole, findByRole } = renderWithThemeAndHookFormContext({
54+
component: <InterfaceGeneration />,
55+
});
5956

60-
const button = getByText('Network Interface Type');
57+
// Wait for the tooltip icon to appear (indicating the disabled state)
58+
await findByRole('button', {
59+
name: 'Your account administrator has enforced that all new Linodes are created with legacy configuration interfaces.',
60+
});
6161

62-
// Expand the "Show More"
63-
await userEvent.click(button);
62+
// Verify both radio buttons are disabled
63+
const radios = getAllByRole('radio');
64+
expect(radios).toHaveLength(2);
6465

65-
// Hover to check for the tooltip
66-
await userEvent.hover(getByText('Network Interface Type'));
66+
for (const radio of radios) {
67+
expect(radio).toBeDisabled();
68+
}
69+
});
6770

68-
await findByText(
69-
'Your account administrator has enforced that all new Linodes are created with legacy configuration interfaces.'
71+
it('enables the radios when account settings allow both interface types', async () => {
72+
const accountSettings = accountSettingsFactory.build({
73+
interfaces_for_new_linodes: 'linode_default_but_legacy_config_allowed',
74+
});
75+
76+
server.use(
77+
http.get(getAccountSettingsAPI, () => {
78+
return HttpResponse.json(accountSettings);
79+
})
7080
);
7181

72-
const radios = getAllByRole('radio');
82+
const { getAllByRole, queryByRole } = renderWithThemeAndHookFormContext({
83+
component: <InterfaceGeneration />,
84+
});
7385

86+
// Wait for radios to render
87+
await new Promise((resolve) => setTimeout(resolve, 100));
88+
89+
// Verify no disabled tooltip appears
90+
expect(
91+
queryByRole('button', {
92+
name: /Your account administrator has enforced/,
93+
})
94+
).toBeNull();
95+
96+
// Verify both radio buttons are enabled
97+
const radios = getAllByRole('radio');
7498
expect(radios).toHaveLength(2);
7599

76100
for (const radio of radios) {
77-
expect(radio).toBeDisabled();
101+
expect(radio).toBeEnabled();
78102
}
79103
});
104+
105+
it('defaults to linode interface when value is not set', async () => {
106+
const accountSettings = accountSettingsFactory.build({
107+
interfaces_for_new_linodes: 'linode_default_but_legacy_config_allowed',
108+
});
109+
110+
server.use(
111+
http.get(getAccountSettingsAPI, () => {
112+
return HttpResponse.json(accountSettings);
113+
})
114+
);
115+
116+
const { getByDisplayValue } = renderWithThemeAndHookFormContext({
117+
component: <InterfaceGeneration />,
118+
useFormOptions: {
119+
defaultValues: {
120+
interface_generation: null,
121+
},
122+
},
123+
});
124+
125+
// Wait for component to render
126+
await new Promise((resolve) => setTimeout(resolve, 100));
127+
128+
// Verify linode radio is selected by default
129+
expect(getByDisplayValue('linode')).toBeChecked();
130+
});
131+
132+
it('allows user to select legacy config interface when enabled', async () => {
133+
const accountSettings = accountSettingsFactory.build({
134+
interfaces_for_new_linodes: 'legacy_config_default_but_linode_allowed',
135+
});
136+
137+
server.use(
138+
http.get(getAccountSettingsAPI, () => {
139+
return HttpResponse.json(accountSettings);
140+
})
141+
);
142+
143+
const { getByDisplayValue } = renderWithThemeAndHookFormContext({
144+
component: <InterfaceGeneration />,
145+
useFormOptions: {
146+
defaultValues: {
147+
interface_generation: 'linode',
148+
},
149+
},
150+
});
151+
152+
// Wait for component to render
153+
await new Promise((resolve) => setTimeout(resolve, 100));
154+
155+
const legacyConfigRadio = getByDisplayValue('legacy_config');
156+
157+
// Click on legacy config radio
158+
await userEvent.click(legacyConfigRadio);
159+
160+
// Verify legacy config is now selected
161+
expect(legacyConfigRadio).toBeChecked();
162+
expect(getByDisplayValue('linode')).not.toBeChecked();
163+
});
164+
165+
it('displays correct labels for both interface types', () => {
166+
const accountSettings = accountSettingsFactory.build({
167+
interfaces_for_new_linodes: 'linode_default_but_legacy_config_allowed',
168+
});
169+
170+
server.use(
171+
http.get(getAccountSettingsAPI, () => {
172+
return HttpResponse.json(accountSettings);
173+
})
174+
);
175+
176+
const { getByText } = renderWithThemeAndHookFormContext({
177+
component: <InterfaceGeneration />,
178+
});
179+
180+
// Verify interface type labels
181+
expect(getByText('Linode Interfaces (Recommended)')).toBeVisible();
182+
expect(
183+
getByText('Configuration Profile Interfaces (Legacy)')
184+
).toBeVisible();
185+
expect(getByText('Network Interface Type')).toBeVisible();
186+
});
80187
});

0 commit comments

Comments
 (0)