Skip to content

Commit a4b4e69

Browse files
authored
upcoming: [UIE-9378] - DBaaS - Display connection pool section and table in Networking tab (#13195)
* upcoming: [UIE-9378] - DBaaS - Display connection pool section and table in Networking tab * Adding changesets * Applying feedback part 1: Updating to exported StyledActionMenuWrapper and applying paginator min, results and page sizes * Feedback part 2: Applying feedback on Stack usage in makeSettingsItemStyles * Removing refretchInterval
1 parent ceee986 commit a4b4e69

File tree

13 files changed

+425
-67
lines changed

13 files changed

+425
-67
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/api-v4": Changed
3+
---
4+
5+
Updated getDatabaseConnectionPools signature to accept params for pagination ([#13195](https://github.com/linode/manager/pull/13195))

packages/api-v4/src/databases/databases.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,12 +371,16 @@ export const getDatabaseEngineConfig = (engine: Engine) =>
371371
/**
372372
* Get a paginated list of connection pools for a database
373373
*/
374-
export const getDatabaseConnectionPools = (databaseID: number) =>
374+
export const getDatabaseConnectionPools = (
375+
databaseID: number,
376+
params?: Params,
377+
) =>
375378
Request<Page<ConnectionPool>>(
376379
setURL(
377380
`${API_ROOT}/databases/postgresql/instances/${encodeURIComponent(databaseID)}/connection-pools`,
378381
),
379382
setMethod('GET'),
383+
setParams(params),
380384
);
381385

382386
/**
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@linode/manager": Changed
3+
---
4+
5+
DBaaS table action menu wrapper and settings item styles are shared and connection pool queries updated for pagination ([#13195](https://github.com/linode/manager/pull/13195))
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+
DBaaS PgBouncer Connection Pools section to be displayed in Networking tab for PostgreSQL database clusters ([#13195](https://github.com/linode/manager/pull/13195))
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { screen } from '@testing-library/react';
2+
import * as React from 'react';
3+
import { describe, it } from 'vitest';
4+
5+
import {
6+
databaseConnectionPoolFactory,
7+
databaseFactory,
8+
} from 'src/factories/databases';
9+
import { makeResourcePage } from 'src/mocks/serverHandlers';
10+
import { renderWithTheme } from 'src/utilities/testHelpers';
11+
12+
import { DatabaseConnectionPools } from './DatabaseConnectionPools';
13+
14+
const mockDatabase = databaseFactory.build({
15+
platform: 'rdbms-default',
16+
private_network: null,
17+
engine: 'postgresql',
18+
id: 1,
19+
});
20+
21+
const mockConnectionPool = databaseConnectionPoolFactory.build({
22+
database: 'defaultdb',
23+
label: 'pool-1',
24+
mode: 'transaction',
25+
size: 10,
26+
username: null,
27+
});
28+
29+
// Hoist query mocks
30+
const queryMocks = vi.hoisted(() => {
31+
return {
32+
useDatabaseConnectionPoolsQuery: vi.fn(),
33+
};
34+
});
35+
36+
vi.mock('@linode/queries', async () => {
37+
const actual = await vi.importActual('@linode/queries');
38+
return {
39+
...actual,
40+
useDatabaseConnectionPoolsQuery: queryMocks.useDatabaseConnectionPoolsQuery,
41+
};
42+
});
43+
44+
describe('DatabaseManageNetworkingDrawer Component', () => {
45+
beforeEach(() => {
46+
vi.resetAllMocks();
47+
});
48+
49+
it('should render PgBouncer Connection Pools field', () => {
50+
queryMocks.useDatabaseConnectionPoolsQuery.mockReturnValue({
51+
data: makeResourcePage([mockConnectionPool]),
52+
isLoading: false,
53+
});
54+
renderWithTheme(<DatabaseConnectionPools database={mockDatabase} />);
55+
56+
const heading = screen.getByRole('heading');
57+
expect(heading.textContent).toBe('Manage PgBouncer Connection Pools');
58+
const addPoolBtnLabel = screen.getByText('Add Pool');
59+
expect(addPoolBtnLabel).toBeInTheDocument();
60+
});
61+
62+
it('should render loading state', () => {
63+
queryMocks.useDatabaseConnectionPoolsQuery.mockReturnValue({
64+
data: makeResourcePage([mockConnectionPool]),
65+
isLoading: true,
66+
});
67+
const loadingTestId = 'circle-progress';
68+
renderWithTheme(<DatabaseConnectionPools database={mockDatabase} />);
69+
70+
const loadingCircle = screen.getByTestId(loadingTestId);
71+
expect(loadingCircle).toBeInTheDocument();
72+
});
73+
74+
it('should render table with connection pool data', () => {
75+
queryMocks.useDatabaseConnectionPoolsQuery.mockReturnValue({
76+
data: makeResourcePage([mockConnectionPool]),
77+
isLoading: false,
78+
});
79+
80+
renderWithTheme(<DatabaseConnectionPools database={mockDatabase} />);
81+
82+
const connectionPoolLabel = screen.getByText(mockConnectionPool.label);
83+
expect(connectionPoolLabel).toBeInTheDocument();
84+
});
85+
86+
it('should render table empty state when no data is provided', () => {
87+
queryMocks.useDatabaseConnectionPoolsQuery.mockReturnValue({
88+
data: makeResourcePage([]),
89+
isLoading: false,
90+
});
91+
92+
renderWithTheme(<DatabaseConnectionPools database={mockDatabase} />);
93+
94+
const emptyStateText = screen.getByText(
95+
"You don't have any connection pools added."
96+
);
97+
expect(emptyStateText).toBeInTheDocument();
98+
});
99+
100+
it('should render error state state when backend responds with error', () => {
101+
queryMocks.useDatabaseConnectionPoolsQuery.mockReturnValue({
102+
error: new Error('Failed to fetch VPC'),
103+
});
104+
105+
renderWithTheme(<DatabaseConnectionPools database={mockDatabase} />);
106+
const errorStateText = screen.getByText(
107+
'There was a problem retrieving your connection pools. Refresh the page or try again later.'
108+
);
109+
expect(errorStateText).toBeInTheDocument();
110+
});
111+
});
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import { useDatabaseConnectionPoolsQuery } from '@linode/queries';
2+
import {
3+
Button,
4+
CircleProgress,
5+
ErrorState,
6+
Hidden,
7+
Stack,
8+
Typography,
9+
} from '@linode/ui';
10+
import { useTheme } from '@mui/material/styles';
11+
import { Pagination } from 'akamai-cds-react-components/Pagination';
12+
import {
13+
Table,
14+
TableBody,
15+
TableCell,
16+
TableHead,
17+
TableHeaderCell,
18+
TableRow,
19+
} from 'akamai-cds-react-components/Table';
20+
import React from 'react';
21+
22+
import { ActionMenu } from 'src/components/ActionMenu/ActionMenu';
23+
import {
24+
MIN_PAGE_SIZE,
25+
PAGE_SIZES,
26+
} from 'src/components/PaginationFooter/PaginationFooter.constants';
27+
import { usePaginationV2 } from 'src/hooks/usePaginationV2';
28+
29+
import {
30+
makeSettingsItemStyles,
31+
StyledActionMenuWrapper,
32+
} from '../../shared.styles';
33+
34+
import type { Database } from '@linode/api-v4';
35+
import type { Action } from 'src/components/ActionMenu/ActionMenu';
36+
37+
interface Props {
38+
database: Database;
39+
disabled?: boolean;
40+
}
41+
42+
export const DatabaseConnectionPools = ({ database }: Props) => {
43+
const { classes } = makeSettingsItemStyles();
44+
const theme = useTheme();
45+
const poolLabelCellStyles = {
46+
flex: '.5 1 20.5%',
47+
};
48+
49+
const pagination = usePaginationV2({
50+
currentRoute: '/databases/$engine/$databaseId/networking',
51+
initialPage: 1,
52+
preferenceKey: `database-connection-pools-pagination`,
53+
});
54+
55+
const {
56+
data: connectionPools,
57+
error: connectionPoolsError,
58+
isLoading: connectionPoolsLoading,
59+
} = useDatabaseConnectionPoolsQuery(database.id, true, {
60+
page: pagination.page,
61+
page_size: pagination.pageSize,
62+
});
63+
64+
const connectionPoolActions: Action[] = [
65+
{
66+
onClick: () => null,
67+
title: 'Edit', // TODO: UIE-9395 Implement edit functionality
68+
},
69+
{
70+
onClick: () => null, // TODO: UIE-9430 Implement delete functionality
71+
title: 'Delete',
72+
},
73+
];
74+
75+
if (connectionPoolsLoading) {
76+
return <CircleProgress />;
77+
}
78+
79+
if (connectionPoolsError) {
80+
return (
81+
<ErrorState errorText="There was a problem retrieving your connection pools. Refresh the page or try again later." />
82+
);
83+
}
84+
85+
return (
86+
<>
87+
<div className={classes.topSection}>
88+
<Stack spacing={0.5}>
89+
<Typography variant="h3">
90+
Manage PgBouncer Connection Pools
91+
</Typography>
92+
<Typography sx={{ maxWidth: '500px' }}>
93+
Manage PgBouncer connection pools to minimize the use of your server
94+
resources.
95+
</Typography>
96+
</Stack>
97+
<Button
98+
buttonType="outlined"
99+
className={classes.actionBtn}
100+
disabled={true}
101+
onClick={() => null}
102+
TooltipProps={{ placement: 'top' }}
103+
>
104+
Add Pool
105+
</Button>
106+
</div>
107+
<div style={{ overflowX: 'auto', width: '100%' }}>
108+
<Table
109+
aria-label={'List of Connection pools'}
110+
style={
111+
{
112+
border: `1px solid ${theme.tokens.alias.Border.Normal}`,
113+
marginTop: '10px',
114+
'--token-component-table-header-outlined-border':
115+
theme.tokens.component.Table.Row.Border,
116+
} as React.CSSProperties
117+
}
118+
>
119+
<TableHead>
120+
<TableRow
121+
headerbackground={
122+
theme.tokens.component.Table.HeaderNested.Background
123+
}
124+
headerborder
125+
>
126+
<TableHeaderCell style={poolLabelCellStyles}>
127+
Pool Label
128+
</TableHeaderCell>
129+
<Hidden smDown>
130+
<TableHeaderCell>Pool Mode</TableHeaderCell>
131+
</Hidden>
132+
<Hidden smDown>
133+
<TableHeaderCell>Pool Size</TableHeaderCell>
134+
</Hidden>
135+
<Hidden smDown>
136+
<TableHeaderCell>Username</TableHeaderCell>
137+
</Hidden>
138+
<TableHeaderCell style={{ maxWidth: 40 }} />
139+
</TableRow>
140+
</TableHead>
141+
<TableBody>
142+
{connectionPools?.data.length === 0 ? (
143+
<TableRow data-testid={'table-row-empty'}>
144+
<TableCell
145+
style={{
146+
display: 'flex',
147+
justifyContent: 'center',
148+
}}
149+
>
150+
You don&apos;t have any connection pools added.
151+
</TableCell>
152+
</TableRow>
153+
) : (
154+
connectionPools?.data.map((pool) => (
155+
<TableRow key={`connection-pool-row-${pool.label}`} zebra>
156+
<TableCell style={poolLabelCellStyles}>
157+
{pool.label}
158+
</TableCell>
159+
<Hidden smDown>
160+
<TableCell>
161+
{`${pool.mode.charAt(0).toUpperCase()}${pool.mode.slice(1)}`}
162+
</TableCell>
163+
</Hidden>
164+
<Hidden smDown>
165+
<TableCell>{pool.size}</TableCell>
166+
</Hidden>
167+
<Hidden smDown>
168+
<TableCell>
169+
{pool.username === null
170+
? 'Reuse inbound user'
171+
: pool.username}
172+
</TableCell>
173+
</Hidden>
174+
<StyledActionMenuWrapper>
175+
<ActionMenu
176+
actionsList={connectionPoolActions}
177+
ariaLabel={`Action menu for connection pool ${pool.label}`}
178+
/>
179+
</StyledActionMenuWrapper>
180+
</TableRow>
181+
))
182+
)}
183+
</TableBody>
184+
</Table>
185+
</div>
186+
{(connectionPools?.results || 0) > MIN_PAGE_SIZE && (
187+
<Pagination
188+
count={connectionPools?.results || 0}
189+
onPageChange={(e: CustomEvent<number>) =>
190+
pagination.handlePageChange(Number(e.detail))
191+
}
192+
onPageSizeChange={(
193+
e: CustomEvent<{ page: number; pageSize: number }>
194+
) => pagination.handlePageSizeChange(Number(e.detail.pageSize))}
195+
page={pagination.page}
196+
pageSize={pagination.pageSize}
197+
pageSizes={PAGE_SIZES}
198+
style={{
199+
borderLeft: `1px solid ${theme.tokens.alias.Border.Normal}`,
200+
borderRight: `1px solid ${theme.tokens.alias.Border.Normal}`,
201+
borderTop: 0,
202+
marginTop: '0',
203+
}}
204+
/>
205+
)}
206+
</>
207+
);
208+
};

0 commit comments

Comments
 (0)