Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: [UIE-8196] - DBaaS create encourage users to add IP allow_list #11124

Merged
merged 1 commit into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@linode/manager": Upcoming Features
---

DBaaS encourage setting access controls during create ([#11124](https://github.com/linode/manager/pull/11124))
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import { fireEvent } from '@testing-library/react';
import * as React from 'react';

import { renderWithTheme } from 'src/utilities/testHelpers';

import { MultipleIPInput } from './MultipleIPInput';

const baseProps = {
Expand Down Expand Up @@ -52,4 +51,43 @@ describe('MultipleIPInput', () => {
{ address: 'ip3' },
]);
});

it('should enable all actions by default', async () => {
const props = {
...baseProps,
ips: [{ address: 'ip1' }, { address: 'ip2' }],
};
const { getByTestId, getByLabelText, getByText } = renderWithTheme(
<MultipleIPInput {...props} />
);
const ip0 = getByLabelText('domain-transfer-ip-0');
const ip1 = getByLabelText('domain-transfer-ip-1');
const closeButton = getByTestId('delete-ip-1').closest('button');
const addButton = getByText('Add an IP').closest('button');

expect(ip0).toBeEnabled();
expect(ip1).toBeEnabled();
expect(closeButton).toBeEnabled();
expect(addButton).toBeEnabled();
});

it('should disable all actions', async () => {
const props = {
...baseProps,
disabled: true,
ips: [{ address: 'ip1' }, { address: 'ip2' }],
};
const { getByTestId, getByLabelText, getByText } = renderWithTheme(
<MultipleIPInput {...props} />
);
const ip0 = getByLabelText('domain-transfer-ip-0');
const ip1 = getByLabelText('domain-transfer-ip-1');
const closeButton = getByTestId('delete-ip-1').closest('button');
const addButton = getByText('Add an IP').closest('button');

expect(ip0).toBeDisabled();
expect(ip1).toBeDisabled();
expect(closeButton).toBeDisabled();
expect(addButton).toBeDisabled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ const useStyles = makeStyles()((theme: Theme) => ({
interface Props {
buttonText?: string;
className?: string;
disabled?: boolean;
error?: string;
forDatabaseAccessControls?: boolean;
forVPCIPv4Ranges?: boolean;
Expand All @@ -78,6 +79,7 @@ export const MultipleIPInput = React.memo((props: Props) => {
const {
buttonText,
className,
disabled,
error,
forDatabaseAccessControls,
forVPCIPv4Ranges,
Expand Down Expand Up @@ -137,6 +139,7 @@ export const MultipleIPInput = React.memo((props: Props) => {
buttonType="secondary"
className={classes.addIP}
compactX
disabled={disabled}
onClick={addNewInput}
>
{buttonText ?? 'Add an IP'}
Expand Down Expand Up @@ -184,6 +187,7 @@ export const MultipleIPInput = React.memo((props: Props) => {
<TextField
InputProps={{
'aria-label': `${title} ip-address-${idx}`,
disabled,
...props.inputProps,
}}
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
Expand All @@ -206,6 +210,7 @@ export const MultipleIPInput = React.memo((props: Props) => {
{(idx > 0 || forDatabaseAccessControls || forVPCIPv4Ranges) && (
<Button
className={classes.button}
disabled={disabled}
onClick={() => removeInput(idx)}
>
<Close data-testid={`delete-ip-${idx}`} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@ import { ErrorState } from 'src/components/ErrorState/ErrorState';
import { FormControl } from 'src/components/FormControl';
import { FormControlLabel } from 'src/components/FormControlLabel';
import { LandingHeader } from 'src/components/LandingHeader';
import { Link } from 'src/components/Link';
import { MultipleIPInput } from 'src/components/MultipleIPInput/MultipleIPInput';
import { Notice } from 'src/components/Notice/Notice';
import { Paper } from 'src/components/Paper';
import { Radio } from 'src/components/Radio/Radio';
Expand All @@ -47,7 +45,7 @@ import { useRegionsQuery } from 'src/queries/regions/regions';
import { formatStorageUnits } from 'src/utilities/formatStorageUnits';
import { handleAPIErrors } from 'src/utilities/formikErrorUtils';
import { getSelectedOptionFromGroupedOptions } from 'src/utilities/getSelectedOptionFromGroupedOptions';
import { ipFieldPlaceholder, validateIPs } from 'src/utilities/ipUtils';
import { validateIPs } from 'src/utilities/ipUtils';
import { scrollErrorIntoViewV2 } from 'src/utilities/scrollErrorIntoViewV2';

import type {
Expand All @@ -64,6 +62,7 @@ import type { Theme } from '@mui/material/styles';
import type { Item } from 'src/components/EnhancedSelect/Select';
import type { PlanSelectionType } from 'src/features/components/PlansPanel/types';
import type { ExtendedIP } from 'src/utilities/ipUtils';
import { DatabaseCreateAccessControls } from './DatabaseCreateAccessControls';

const useStyles = makeStyles()((theme: Theme) => ({
btnCtn: {
Expand Down Expand Up @@ -622,44 +621,12 @@ const DatabaseCreate = () => {
</FormControl>
</Grid>
<Divider spacingBottom={12} spacingTop={26} />
<Grid>
<Typography style={{ marginBottom: 4 }} variant="h2">
Add Access Controls
</Typography>
<Typography>
Add any IPv4 address or range that should be authorized to access
this cluster.
</Typography>
<Typography>
By default, all public and private connections are denied.{' '}
<Link to="https://techdocs.akamai.com/cloud-computing/docs/manage-access-controls">
Learn more
</Link>
.
</Typography>
<Typography style={{ marginTop: 16 }}>
You can add or modify access controls after your database cluster is
active.{' '}
</Typography>
<Grid style={{ marginTop: 24, maxWidth: 450 }}>
{ipErrorsFromAPI
? ipErrorsFromAPI.map((apiError: APIError) => (
<Notice
key={apiError.reason}
text={apiError.reason}
variant="error"
/>
))
: null}
<MultipleIPInput
ips={values.allow_list}
onBlur={handleIPBlur}
onChange={(address) => setFieldValue('allow_list', address)}
placeholder={ipFieldPlaceholder}
title="Allowed IP Address(es) or Range(s)"
/>
</Grid>
</Grid>
<DatabaseCreateAccessControls
errors={ipErrorsFromAPI}
ips={values.allow_list}
onBlur={handleIPBlur}
onChange={(ips: ExtendedIP[]) => setFieldValue('allow_list', ips)}
/>
</Paper>
<Grid className={classes.btnCtn}>
<Typography className={classes.createText}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { fireEvent } from '@testing-library/react';
import React from 'react';
import { renderWithTheme } from 'src/utilities/testHelpers';
import { DatabaseCreateAccessControls } from './DatabaseCreateAccessControls';
import { IsDatabasesEnabled, useIsDatabasesEnabled } from '../utilities';

vi.mock('src/features/Databases/utilities');

describe('DatabaseCreateAccessControls', () => {
beforeEach(() => {
vi.resetAllMocks();
});

it('Should render enabled', () => {
vi.mocked(useIsDatabasesEnabled).mockReturnValue({
isDatabasesV2GA: true,
} as IsDatabasesEnabled);

const ips = [{ address: '' }];
const { container, getAllByText, getAllByTestId } = renderWithTheme(
<DatabaseCreateAccessControls
ips={ips}
onBlur={() => {}}
onChange={() => {}}
/>
);

expect(getAllByText('Add Access Controls')).toHaveLength(1);
expect(getAllByTestId('domain-transfer-input')).toHaveLength(1);
expect(getAllByTestId('button')).toHaveLength(1);

const specificRadio = container.querySelector(
'[data-qa-dbaas-radio="Specific"]'
);
const noneRadio = container.querySelector('[data-qa-dbaas-radio="None"]');
const enabledButtons = container.querySelectorAll(
'[aria-disabled="false"]'
);

expect(specificRadio).toBeInTheDocument();
expect(noneRadio).toBeInTheDocument();
expect(enabledButtons).toHaveLength(1);
});

it('Should render ips', () => {
vi.mocked(useIsDatabasesEnabled).mockReturnValue({
isDatabasesV2GA: true,
} as IsDatabasesEnabled);

const ips = [
{ address: '1.1.1.1/32' },
{ address: '2.2.2.2' },
{ address: '3.3.3.3/128' },
];
const { container, getAllByText, getAllByTestId } = renderWithTheme(
<DatabaseCreateAccessControls
ips={ips}
onBlur={() => {}}
onChange={() => {}}
/>
);

expect(getAllByText('Add Access Controls')).toHaveLength(1);
expect(getAllByTestId('domain-transfer-input')).toHaveLength(3);
expect(getAllByTestId('button')).toHaveLength(3);

const specificRadio = container.querySelector(
'[data-qa-dbaas-radio="Specific"]'
);
const noneRadio = container.querySelector('[data-qa-dbaas-radio="None"]');
const enabledButtons = container.querySelectorAll(
'[aria-disabled="false"]'
);

expect(specificRadio).toBeInTheDocument();
expect(noneRadio).toBeInTheDocument();
expect(enabledButtons).toHaveLength(3);
});

it('Should disable ips', () => {
vi.mocked(useIsDatabasesEnabled).mockReturnValue({
isDatabasesV2GA: true,
} as IsDatabasesEnabled);

const ips = [{ address: '1.1.1.1/32' }];
const { container, getAllByText, getAllByTestId } = renderWithTheme(
<DatabaseCreateAccessControls
ips={ips}
onBlur={() => {}}
onChange={() => {}}
/>
);

expect(getAllByText('Add Access Controls')).toHaveLength(1);
expect(getAllByTestId('domain-transfer-input')).toHaveLength(1);
expect(getAllByTestId('button')).toHaveLength(1);

let enabledButtons = container.querySelectorAll('[aria-disabled="false"]');
expect(enabledButtons).toHaveLength(1);

const noneRadio = container.querySelector('[data-qa-dbaas-radio="None"]');
fireEvent.click(noneRadio as Element);

enabledButtons = container.querySelectorAll('[aria-disabled="false"]');
expect(enabledButtons).toHaveLength(0);
});
});
Loading