Skip to content

Commit

Permalink
feat: [UIE-8196] - DBaaS create encourage users to add IP allow_list
Browse files Browse the repository at this point in the history
  • Loading branch information
corya-akamai committed Oct 22, 2024
1 parent 7735a37 commit a63bd41
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 42 deletions.
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

0 comments on commit a63bd41

Please sign in to comment.