Skip to content

Commit 473315f

Browse files
authored
fix: [UIE-9003] - DBaaS - node selector options for premium and provide plan disabling behavior in resize (linode#12634)
* fix: [UIE-9003] - DBaaS - expand node selector options for premium plans and provide disabling behavior for plans by region in resize * Adding changesets * Updating added changeset * Updating logic so that region queries that allow disabling behavior from PlansPanel are only made for new database clusters * Applying additional feedback
1 parent dddbb3f commit 473315f

File tree

9 files changed

+324
-37
lines changed

9 files changed

+324
-37
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Added
3+
---
4+
5+
PlansPanel additionalBanners property for rendering additional banners and disabled property for DatabaseNodeSelector ([#12634](https://github.com/linode/manager/pull/12634))
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Fixed
3+
---
4+
5+
DBaaS Create and Resize node selector options for premium plans and region disabling behavior and handling not being applied in Resize ([#12634](https://github.com/linode/manager/pull/12634))

packages/manager/src/features/Databases/DatabaseCreate/DatabaseNodeSelector.test.tsx

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,56 @@ import { waitForElementToBeRemoved } from '@testing-library/react';
22
import userEvent from '@testing-library/user-event';
33
import * as React from 'react';
44

5+
import { databaseTypeFactory, planSelectionTypeFactory } from 'src/factories';
56
import { mockMatchMedia, renderWithTheme } from 'src/utilities/testHelpers';
67

78
import { DatabaseCreate } from './DatabaseCreate';
9+
import { DatabaseNodeSelector } from './DatabaseNodeSelector';
10+
11+
import type { ClusterSize, Engine } from '@linode/api-v4';
12+
import type { PlanSelectionWithDatabaseType } from 'src/features/components/PlansPanel/types';
813

914
const loadingTestId = 'circle-progress';
1015

16+
const mockDisplayTypes = [
17+
planSelectionTypeFactory.build({
18+
class: 'standard',
19+
}),
20+
planSelectionTypeFactory.build({
21+
class: 'nanode',
22+
}),
23+
planSelectionTypeFactory.build({
24+
class: 'dedicated',
25+
}),
26+
planSelectionTypeFactory.build({
27+
class: 'premium',
28+
id: 'premium-32',
29+
label: 'Premium 32 GB',
30+
}),
31+
];
32+
33+
const mockCurrentPlan = databaseTypeFactory.build({
34+
class: 'premium',
35+
id: 'premium-32',
36+
label: 'Premium 32 GB',
37+
}) as PlanSelectionWithDatabaseType;
38+
39+
const mockClusterSize: ClusterSize = 1;
40+
const mockEngine: Engine = 'mysql';
41+
42+
const mockProps = {
43+
currentClusterSize: mockClusterSize,
44+
currentPlan: mockCurrentPlan,
45+
disabled: false,
46+
displayTypes: mockDisplayTypes,
47+
error: '',
48+
handleNodeChange: vi.fn(),
49+
selectedClusterSize: mockClusterSize,
50+
selectedEngine: mockEngine,
51+
selectedPlan: mockCurrentPlan,
52+
selectedTab: 0,
53+
};
54+
1155
beforeAll(() => mockMatchMedia());
1256

1357
describe('database node selector', () => {
@@ -17,7 +61,7 @@ describe('database node selector', () => {
1761
enabled: true,
1862
},
1963
};
20-
it('should render 3 nodes for dedicated tab', async () => {
64+
it('should render 3 node options for dedicated tab', async () => {
2165
const { getByTestId } = renderWithTheme(<DatabaseCreate />, {
2266
flags,
2367
});
@@ -30,7 +74,7 @@ describe('database node selector', () => {
3074
expect(getByTestId('database-node-3')).toBeInTheDocument();
3175
});
3276

33-
it('should render 2 nodes for shared tab', async () => {
77+
it('should render 2 node options for shared tab', async () => {
3478
const { getAllByRole, getByTestId } = renderWithTheme(<DatabaseCreate />, {
3579
flags,
3680
});
@@ -44,4 +88,34 @@ describe('database node selector', () => {
4488
expect(getByTestId('database-node-1')).toBeInTheDocument();
4589
expect(getByTestId('database-node-3')).toBeInTheDocument();
4690
});
91+
92+
it('should render 3 node options for premium tab', async () => {
93+
const { getByTestId, getAllByRole } = renderWithTheme(<DatabaseCreate />, {
94+
flags,
95+
});
96+
expect(getByTestId(loadingTestId)).toBeInTheDocument();
97+
await waitForElementToBeRemoved(getByTestId(loadingTestId));
98+
99+
const premiumTab = getAllByRole('tab')[2];
100+
await userEvent.click(premiumTab);
101+
102+
expect(getByTestId('database-nodes').childNodes.length).equal(3);
103+
expect(getByTestId('database-node-1')).toBeInTheDocument();
104+
expect(getByTestId('database-node-2')).toBeInTheDocument();
105+
expect(getByTestId('database-node-3')).toBeInTheDocument();
106+
});
107+
108+
it('should disable 3 node options when disabled is true', async () => {
109+
const { getByTestId } = renderWithTheme(
110+
<DatabaseNodeSelector {...mockProps} disabled />,
111+
{
112+
flags,
113+
}
114+
);
115+
116+
expect(getByTestId('database-nodes')).toHaveAttribute(
117+
'aria-disabled',
118+
'true'
119+
);
120+
});
47121
});

packages/manager/src/features/Databases/DatabaseCreate/DatabaseNodeSelector.tsx

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export interface NodePricing {
3131
interface Props {
3232
currentClusterSize?: ClusterSize | undefined;
3333
currentPlan?: PlanSelectionWithDatabaseType | undefined;
34+
disabled?: boolean;
3435
displayTypes: PlanSelectionType[];
3536
error?: string;
3637
handleNodeChange: (value: ClusterSize) => void;
@@ -44,6 +45,7 @@ export const DatabaseNodeSelector = (props: Props) => {
4445
const {
4546
currentClusterSize,
4647
currentPlan,
48+
disabled,
4749
displayTypes,
4850
error,
4951
handleNodeChange,
@@ -79,6 +81,8 @@ export const DatabaseNodeSelector = (props: Props) => {
7981
(type) => type.class === 'dedicated'
8082
);
8183

84+
const hasPremium = displayTypes.some((type) => type.class === 'premium');
85+
8286
const currentChip = currentClusterSize && initialTab === selectedTab && (
8387
<StyledChip
8488
aria-label="This is your current number of nodes"
@@ -104,7 +108,12 @@ export const DatabaseNodeSelector = (props: Props) => {
104108
},
105109
];
106110

107-
if (hasDedicated && selectedTab === 0) {
111+
const isDedicated = hasDedicated && selectedTab === 0;
112+
const isPremium = hasPremium && selectedTab === 2;
113+
114+
const displayTwoNodesOption = isDedicated || isPremium;
115+
116+
if (displayTwoNodesOption) {
108117
options.push({
109118
label: (
110119
<Typography component="div">
@@ -157,14 +166,14 @@ export const DatabaseNodeSelector = (props: Props) => {
157166
upgrades and maintenance.
158167
</Typography>
159168
<FormControl
160-
disabled={isRestricted}
169+
disabled={isRestricted || disabled}
161170
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
162171
handleNodeChange(+e.target.value as ClusterSize);
163172
}}
164173
>
165174
{error ? <Notice text={error} variant="error" /> : null}
166175
<RadioGroup
167-
aria-disabled={isRestricted}
176+
aria-disabled={isRestricted || disabled}
168177
data-testid="database-nodes"
169178
style={{ marginBottom: 0, marginTop: 0 }}
170179
value={selectedClusterSize ?? ''}

packages/manager/src/features/Databases/DatabaseDetail/DatabaseResize/DatabaseResize.test.tsx

Lines changed: 134 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { regionAvailabilityFactory, regionFactory } from '@linode/utilities';
12
import { waitForElementToBeRemoved } from '@testing-library/react';
23
import userEvent from '@testing-library/user-event';
34
import * as React from 'react';
@@ -25,6 +26,50 @@ const engine = 'mysql';
2526
const isResizeEnabled = true;
2627
const loadingTestId = 'circle-progress';
2728

29+
const mockDedicatedTypes = [
30+
databaseTypeFactory.build({
31+
class: 'dedicated',
32+
disk: 81920,
33+
id: 'g6-dedicated-2',
34+
label: 'Dedicated 4 GB',
35+
memory: 4096,
36+
}),
37+
databaseTypeFactory.build({
38+
class: 'dedicated',
39+
disk: 163840,
40+
id: 'g6-dedicated-4',
41+
label: 'Dedicated 8 GB',
42+
memory: 8192,
43+
}),
44+
];
45+
46+
const availabilities = [
47+
regionAvailabilityFactory.build({
48+
plan: 'premium-32',
49+
region: 'us-east',
50+
available: false,
51+
}),
52+
];
53+
54+
const queryMocks = vi.hoisted(() => ({
55+
useRegionAvailabilityQuery: vi.fn().mockReturnValue({ data: [] }),
56+
useRegionQuery: vi.fn().mockReturnValue({ data: {} }),
57+
}));
58+
59+
vi.mock('@linode/queries', async () => {
60+
const actual = await vi.importActual('@linode/queries');
61+
return {
62+
...actual,
63+
useRegionQuery: queryMocks.useRegionQuery,
64+
useRegionAvailabilityQuery: queryMocks.useRegionAvailabilityQuery,
65+
};
66+
});
67+
68+
// Mock response from region availability query to simulate limited availability for premium-32 plan in us-ord region (Chicago)
69+
queryMocks.useRegionAvailabilityQuery.mockReturnValue({
70+
data: availabilities,
71+
});
72+
2873
beforeAll(() => mockMatchMedia());
2974

3075
describe('database resize', () => {
@@ -187,15 +232,6 @@ describe('database resize', () => {
187232
}),
188233
...databaseTypeFactory.buildList(7, { class: 'standard' }),
189234
];
190-
const mockDedicatedTypes = [
191-
databaseTypeFactory.build({
192-
class: 'dedicated',
193-
disk: 81920,
194-
id: 'g6-dedicated-2',
195-
label: 'Dedicated 4 GB',
196-
memory: 4096,
197-
}),
198-
];
199235

200236
server.use(
201237
http.get('*/databases/types', () => {
@@ -344,24 +380,6 @@ describe('database resize', () => {
344380

345381
describe('on rendering of page and isDatabasesV2GA is true and the Dedicated CPU tab is preselected', () => {
346382
beforeEach(() => {
347-
// Mock database types
348-
const mockDedicatedTypes = [
349-
databaseTypeFactory.build({
350-
class: 'dedicated',
351-
disk: 81920,
352-
id: 'g6-dedicated-2',
353-
label: 'Dedicated 4 GB',
354-
memory: 4096,
355-
}),
356-
databaseTypeFactory.build({
357-
class: 'dedicated',
358-
disk: 163840,
359-
id: 'g6-dedicated-4',
360-
label: 'Dedicated 8 GB',
361-
memory: 8192,
362-
}),
363-
];
364-
365383
// Mock database types
366384
const standardTypes = [
367385
databaseTypeFactory.build({
@@ -500,4 +518,93 @@ describe('database resize', () => {
500518
expect(getByText('Shared CPU')).toHaveAttribute('aria-disabled', 'true');
501519
});
502520
});
521+
522+
describe('on rendering of page and databasePremium flag is true and the current plan for the cluster is unavailable', () => {
523+
beforeEach(() => {
524+
// Mock database types
525+
const standardTypes = [
526+
databaseTypeFactory.build({
527+
class: 'nanode',
528+
id: 'g6-nanode-1',
529+
label: `New DBaaS - Nanode 1 GB`,
530+
memory: 1024,
531+
}),
532+
];
533+
const premiumTypes = [
534+
databaseTypeFactory.build({
535+
class: 'premium',
536+
id: 'premium-32',
537+
label: `DBaaS - Premium 32 GB`,
538+
memory: 1024,
539+
}),
540+
];
541+
542+
const mockRegion = regionFactory.build({
543+
capabilities: ['VPCs'],
544+
id: 'us-east',
545+
label: 'Newark, NJ',
546+
});
547+
queryMocks.useRegionQuery.mockReturnValue({
548+
data: mockRegion,
549+
});
550+
551+
server.use(
552+
http.get('*/databases/types', () => {
553+
return HttpResponse.json(
554+
makeResourcePage([
555+
...mockDedicatedTypes,
556+
...standardTypes,
557+
...premiumTypes,
558+
])
559+
);
560+
}),
561+
http.get('*/account', () => {
562+
const account = accountFactory.build({
563+
capabilities: ['Managed Databases', 'Managed Databases Beta'],
564+
});
565+
return HttpResponse.json(account);
566+
})
567+
);
568+
});
569+
570+
it('should disable node selection and display unavailable current plan notice', async () => {
571+
const databaseWithPremiumSelection = {
572+
...mockDatabase,
573+
type: 'premium-32',
574+
region: 'us-east',
575+
};
576+
577+
const flags = {
578+
dbaasV2: {
579+
beta: false,
580+
enabled: true,
581+
},
582+
databasePremium: true,
583+
};
584+
585+
const { getByTestId, getAllByText } = renderWithTheme(
586+
<DatabaseDetailContext.Provider
587+
value={{
588+
database: databaseWithPremiumSelection,
589+
engine,
590+
isResizeEnabled,
591+
}}
592+
>
593+
<DatabaseResize />
594+
</DatabaseDetailContext.Provider>,
595+
{ flags }
596+
);
597+
expect(getByTestId(loadingTestId)).toBeInTheDocument();
598+
await waitForElementToBeRemoved(getByTestId(loadingTestId));
599+
600+
expect(getByTestId('database-nodes')).toHaveAttribute(
601+
'aria-disabled',
602+
'true'
603+
);
604+
const expectedMessage =
605+
'Warning: Your current plan is currently unavailable and it can\u{2019}t be used to resize the cluster. You can only resize the cluster using other available plans.';
606+
const unavailableNotice = getAllByText(expectedMessage);
607+
expect(unavailableNotice[0]).toBeInTheDocument();
608+
});
609+
});
503610
});

0 commit comments

Comments
 (0)