Skip to content

Commit b3f1ee2

Browse files
authored
upcoming: [UIE-9395] - Edit Connection Pool Drawer (#13304)
## Description 📝 Add Edit Connection Pool drawer to the Database Networking -> PgBouncer Connection Pools section ## How to test 🧪 ### Prerequisites (How to setup test environment) - Ensure you have the Database PgBouncer flag on and the legacy MSW on ### Verification steps (How to verify changes) - [ ] Go to a postgresql Database cluster's networking tab and scroll to the PgBouncer Connection Pools section - [ ] Edit an existing connection pool in the table via action menu dropdown - [ ] The edit drawer should have the connection pool's details prefilled with the label disabled - [ ] Make edits, click save, and check the network request - [ ] Test error states, edits should not be saved if the user clicks cancel or closes out of the drawer without saving ``` pnpm test DatabaseEditConnectionPoolDrawer ```
1 parent 8c8e4d4 commit b3f1ee2

File tree

10 files changed

+420
-23
lines changed

10 files changed

+420
-23
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Upcoming Features
3+
---
4+
5+
Add Edit Connection Pool Drawer ([#13304](https://github.com/linode/manager/pull/13304))

packages/manager/src/features/Databases/DatabaseDetail/DatabaseNetworking/DatabaseAddConnectionPoolDrawer.tsx

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ import * as React from 'react';
1515
import { Controller, useForm, useWatch } from 'react-hook-form';
1616

1717
import { Link } from 'src/components/Link';
18+
import {
19+
databaseNamesOptions,
20+
defaultUsername,
21+
poolModeOptions,
22+
usernameOptions,
23+
} from 'src/features/Databases/constants';
1824

1925
import { MANAGE_CONNECTION_POOLS_LEARN_MORE_LINK } from '../../constants';
2026

@@ -26,18 +32,6 @@ interface Props {
2632
open: boolean;
2733
}
2834

29-
const defaultUsername = 'Reuse inbound user'; // Represented as null in the API
30-
const poolModeOptions = [
31-
{ label: 'Transaction', value: 'transaction' },
32-
{ label: 'Session', value: 'session' },
33-
{ label: 'Statement', value: 'statement' },
34-
];
35-
const databaseNamesOptions = [{ label: 'defaultdb', value: 'defaultdb' }]; // Currently the only option for the database name field, but more may be introduced later.
36-
const usernameOptions = [
37-
{ label: defaultUsername, value: defaultUsername },
38-
{ label: 'akmadmin', value: 'akmadmin' },
39-
]; // Currently the only options for the username field
40-
4135
export const DatabaseAddConnectionPoolDrawer = (props: Props) => {
4236
const { databaseId, onClose, open } = props;
4337
const { enqueueSnackbar } = useSnackbar();

packages/manager/src/features/Databases/DatabaseDetail/DatabaseNetworking/DatabaseConnectionPoolRow.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,23 @@ interface Props {
1414
* Function called when the delete button in the Action Menu is pressed.
1515
*/
1616
onDelete: (pool: ConnectionPool) => void;
17+
/**
18+
* Function called when the edit button in the Action Menu is pressed.
19+
*/
20+
onEdit: (pool: ConnectionPool) => void;
1721
/**
1822
* Payment method type and data.
1923
*/
2024
pool: ConnectionPool;
2125
}
2226

2327
export const DatabaseConnectionPoolRow = (props: Props) => {
24-
const { pool, onDelete } = props;
28+
const { pool, onDelete, onEdit } = props;
2529

2630
const connectionPoolActions: Action[] = [
2731
{
28-
onClick: () => null,
29-
title: 'Edit', // TODO: UIE-9395 Implement edit functionality
32+
onClick: () => onEdit(pool),
33+
title: 'Edit',
3034
},
3135
{
3236
onClick: () => onDelete(pool),

packages/manager/src/features/Databases/DatabaseDetail/DatabaseNetworking/DatabaseConnectionPools.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ import { ServiceURI } from '../ServiceURI';
3535
import { DatabaseAddConnectionPoolDrawer } from './DatabaseAddConnectionPoolDrawer';
3636
import { DatabaseConnectionPoolDeleteDialog } from './DatabaseConnectionPoolDeleteDialog';
3737
import { DatabaseConnectionPoolRow } from './DatabaseConnectionPoolRow';
38+
import { DatabaseEditConnectionPoolDrawer } from './DatabaseEditConnectionPoolDrawer';
3839

39-
import type { Database } from '@linode/api-v4';
40+
import type { ConnectionPool, Database } from '@linode/api-v4';
4041

4142
interface Props {
4243
database: Database;
@@ -49,8 +50,10 @@ export const DatabaseConnectionPools = ({ database }: Props) => {
4950
const isDatabaseInactive = database.status !== 'active';
5051

5152
const [deletePoolLabelSelection, setDeletePoolLabelSelection] =
52-
React.useState<null | string>();
53+
React.useState<null | string>(null);
5354
const [isAddPoolDrawerOpen, setIsAddPoolDrawerOpen] = React.useState(false);
55+
const [editPoolSelection, setEditPoolSelection] =
56+
React.useState<ConnectionPool | null>(null);
5457

5558
const pagination = usePaginationV2({
5659
currentRoute: '/databases/$engine/$databaseId/networking',
@@ -161,6 +164,7 @@ export const DatabaseConnectionPools = ({ database }: Props) => {
161164
<DatabaseConnectionPoolRow
162165
key={pool.label}
163166
onDelete={() => setDeletePoolLabelSelection(pool.label)}
167+
onEdit={() => setEditPoolSelection(pool)}
164168
pool={pool}
165169
/>
166170
))
@@ -199,6 +203,14 @@ export const DatabaseConnectionPools = ({ database }: Props) => {
199203
onClose={() => setIsAddPoolDrawerOpen(false)}
200204
open={isAddPoolDrawerOpen}
201205
/>
206+
{editPoolSelection && (
207+
<DatabaseEditConnectionPoolDrawer
208+
databaseId={database.id}
209+
onClose={() => setEditPoolSelection(null)}
210+
open={Boolean(editPoolSelection)}
211+
pool={editPoolSelection}
212+
/>
213+
)}
202214
</>
203215
);
204216
};
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
import { screen } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import * as React from 'react';
4+
import { describe, it } from 'vitest';
5+
6+
import { databaseConnectionPoolFactory } from 'src/factories';
7+
import { renderWithTheme } from 'src/utilities/testHelpers';
8+
9+
import { DatabaseEditConnectionPoolDrawer } from './DatabaseEditConnectionPoolDrawer';
10+
11+
const mockProps = {
12+
databaseId: 123,
13+
onClose: vi.fn(),
14+
open: true,
15+
pool: databaseConnectionPoolFactory.build({
16+
label: 'test-pool',
17+
mode: 'session',
18+
size: 22,
19+
username: 'akmadmin',
20+
}),
21+
};
22+
23+
// Hoist query mocks
24+
const queryMocks = vi.hoisted(() => {
25+
return {
26+
useUpdateDatabaseConnectionPoolMutation: vi.fn(),
27+
};
28+
});
29+
30+
vi.mock('@linode/queries', async () => {
31+
const actual = await vi.importActual('@linode/queries');
32+
return {
33+
...actual,
34+
useUpdateDatabaseConnectionPoolMutation:
35+
queryMocks.useUpdateDatabaseConnectionPoolMutation,
36+
};
37+
});
38+
39+
describe('DatabaseEditConnectionPoolDrawer Component', () => {
40+
beforeEach(() => {
41+
vi.resetAllMocks();
42+
queryMocks.useUpdateDatabaseConnectionPoolMutation.mockReturnValue({});
43+
queryMocks.useUpdateDatabaseConnectionPoolMutation.mockReturnValue({
44+
mutateAsync: vi.fn().mockResolvedValue({}),
45+
isLoading: false,
46+
reset: vi.fn(),
47+
});
48+
});
49+
50+
it('Should render the drawer title, prefilled inputs, and actions', () => {
51+
renderWithTheme(<DatabaseEditConnectionPoolDrawer {...mockProps} />);
52+
53+
const drawerTitle = screen.getByText('Edit Connection Pool');
54+
expect(drawerTitle).toBeInTheDocument();
55+
56+
const poolLabelInput = screen.getByLabelText('Pool Label');
57+
expect(poolLabelInput).toBeVisible();
58+
expect(poolLabelInput).toHaveValue('test-pool');
59+
// Label should not be editable
60+
expect(poolLabelInput).not.toBeEnabled();
61+
62+
const databaseNameInput = screen.getByLabelText('Database Name');
63+
const poolModeInput = screen.getByLabelText('Pool Mode');
64+
const poolSizeInput = screen.getByLabelText('Pool Size');
65+
const usernameInput = screen.getByLabelText('Username');
66+
67+
expect(databaseNameInput).toBeVisible();
68+
expect(databaseNameInput).toHaveValue('defaultdb');
69+
70+
expect(poolModeInput).toBeVisible();
71+
expect(poolModeInput).toHaveValue('Session');
72+
73+
expect(poolSizeInput).toBeVisible();
74+
expect(poolSizeInput).toHaveValue(22);
75+
76+
expect(usernameInput).toBeVisible();
77+
expect(usernameInput).toHaveValue('akmadmin');
78+
79+
const saveBtn = screen.getByText('Save');
80+
const cancelBtn = screen.getByText('Cancel');
81+
expect(saveBtn).toBeVisible();
82+
expect(cancelBtn).toBeVisible();
83+
});
84+
85+
it('Should show error notice on root error', async () => {
86+
const mockErrorMessage = 'This is a root level error';
87+
queryMocks.useUpdateDatabaseConnectionPoolMutation.mockReturnValue({
88+
mutateAsync: vi
89+
.fn()
90+
.mockRejectedValue([{ field: 'root', reason: mockErrorMessage }]),
91+
isLoading: false,
92+
reset: vi.fn(),
93+
});
94+
95+
renderWithTheme(<DatabaseEditConnectionPoolDrawer {...mockProps} />);
96+
97+
// Edit and submit the filled form
98+
const poolModeSelect = screen.getByLabelText('Pool Mode');
99+
await userEvent.click(poolModeSelect);
100+
await userEvent.click(screen.getByText('Statement'));
101+
const saveBtn = screen.getByText('Save');
102+
await userEvent.click(saveBtn);
103+
104+
// Check that the error notice is displayed
105+
const errorNotice = screen.getByText(mockErrorMessage);
106+
expect(errorNotice).toBeInTheDocument();
107+
});
108+
109+
it('Should display inline errors', async () => {
110+
queryMocks.useUpdateDatabaseConnectionPoolMutation.mockReturnValue({
111+
mutateAsync: vi.fn().mockRejectedValue([
112+
{ field: 'size', reason: 'Size error message' },
113+
{ field: 'mode', reason: 'Mode error message' },
114+
{ field: 'database', reason: 'Database error message' },
115+
{ field: 'username', reason: 'Username error message' },
116+
]),
117+
isLoading: false,
118+
reset: vi.fn(),
119+
});
120+
121+
renderWithTheme(<DatabaseEditConnectionPoolDrawer {...mockProps} />);
122+
123+
// Edit and submit the filled form
124+
const poolModeSelect = screen.getByLabelText('Pool Mode');
125+
await userEvent.click(poolModeSelect);
126+
await userEvent.click(screen.getByText('Statement'));
127+
const saveBtn = screen.getByText('Save');
128+
await userEvent.click(saveBtn);
129+
130+
// Check that inline errors are displayed
131+
const sizeError = screen.getByText('Size error message');
132+
const modeError = screen.getByText('Mode error message');
133+
const databaseError = screen.getByText('Database error message');
134+
const usernameError = screen.getByText('Username error message');
135+
expect(sizeError).toBeVisible();
136+
expect(modeError).toBeVisible();
137+
expect(databaseError).toBeVisible();
138+
expect(usernameError).toBeVisible();
139+
});
140+
});

0 commit comments

Comments
 (0)