WebDiscover: Check for RDS length before setting a limit for listing DBs (#27194)

* Fix bug: Check for fetched rds results before determining limit for fetching db servers

* Address crs
This commit is contained in:
Lisa Kim 2023-06-05 11:56:08 -07:00 committed by GitHub
parent 8442a4d74a
commit 8b994fcec0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 189 additions and 71 deletions

View file

@ -15,11 +15,8 @@
*/
import React, { useState } from 'react';
import { Box, ButtonPrimary, Text, Flex, ButtonSecondary } from 'design';
import FieldSelect from 'shared/components/FieldSelect';
import { Option } from 'shared/components/Select';
import { requiredField } from 'shared/components/Validation/rules';
import Validation, { Validator } from 'shared/components/Validation';
import { Box, Text, Flex, ButtonSecondary, LabelInput } from 'design';
import Select, { Option } from 'shared/components/Select';
import { Refresh as RefreshIcon } from 'design/Icon';
import { awsRegionMap, Regions } from 'teleport/services/integrations';
@ -27,83 +24,59 @@ import { awsRegionMap, Regions } from 'teleport/services/integrations';
export function AwsRegionSelector({
onFetch,
onRefresh,
disableFetch,
disableSelector,
clear,
}: {
onFetch(region: Regions): void;
onRefresh(): void;
disableFetch: boolean;
disableSelector: boolean;
clear(): void;
}) {
const [selectedRegion, setSelectedRegion] = useState<RegionOption>();
function handleFetch(validator: Validator) {
if (!validator.validate()) {
return;
}
onFetch(selectedRegion.value);
}
function handleRegionSelect(option: RegionOption) {
clear();
setSelectedRegion(option);
onFetch(option.value);
}
return (
<Validation>
{({ validator }) => (
<Box>
<Text mt={4}>
Select the AWS Region you would like to see databases for:
</Text>
<Flex alignItems="center" gap={3} mt={2} mb={3}>
<Box width="320px">
<FieldSelect
label="AWS Region"
rule={requiredField('Region is required')}
placeholder="Select a Region"
isSearchable
isSimpleValue
value={selectedRegion}
onChange={handleRegionSelect}
options={options}
isDisabled={disableSelector}
/>
</Box>
<Flex alignItems="center">
<ButtonPrimary
disabled={disableFetch || !selectedRegion}
onClick={() => handleFetch(validator)}
width="160px"
height="40px"
mt={1}
>
Fetch Databases
</ButtonPrimary>
<ButtonSecondary
onClick={onRefresh}
ml={3}
mt={1}
title="Refresh database table"
height="40px"
width="30px"
css={`
&:disabled {
opacity: 0.35;
pointer-events: none;
}
`}
disabled={disableSelector || !disableFetch}
>
<RefreshIcon fontSize={3} />
</ButtonSecondary>
</Flex>
</Flex>
<Box>
<Text mt={4}>
Select the AWS Region you would like to see databases for:
</Text>
<Flex alignItems="center" gap={3} mt={2} mb={3}>
<Box width="320px" mb={4}>
<LabelInput htmlFor={'select'}>AWS Region</LabelInput>
<Select
inputId="select"
isSearchable
value={selectedRegion}
onChange={handleRegionSelect}
options={options}
placeholder="Select a region"
autoFocus
isDisabled={disableSelector}
/>
</Box>
)}
</Validation>
<ButtonSecondary
onClick={onRefresh}
mt={1}
title="Refresh database table"
height="40px"
width="30px"
css={`
&:disabled {
opacity: 0.35;
pointer-events: none;
}
`}
disabled={disableSelector || !selectedRegion}
>
<RefreshIcon fontSize={3} />
</ButtonSecondary>
</Flex>
</Box>
);
}

View file

@ -27,7 +27,6 @@ export default {
export const AwsRegionsSelectorDisabled = () => (
<AwsRegionSelector
onFetch={() => null}
disableFetch={true}
onRefresh={() => null}
disableSelector={true}
clear={() => null}
@ -37,7 +36,6 @@ export const AwsRegionsSelectorDisabled = () => (
export const AwsRegionsSelectorEnabled = () => (
<AwsRegionSelector
onFetch={() => null}
disableFetch={false}
onRefresh={() => null}
disableSelector={false}
clear={() => null}
@ -47,7 +45,6 @@ export const AwsRegionsSelectorEnabled = () => (
export const AwsRegionsSelectorRefreshEnabled = () => (
<AwsRegionSelector
onFetch={() => null}
disableFetch={true}
onRefresh={() => null}
disableSelector={false}
clear={() => null}

View file

@ -0,0 +1,144 @@
/**
* Copyright 2023 Gravitational, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import React from 'react';
import { MemoryRouter } from 'react-router';
import { render, screen, fireEvent } from 'design/utils/testing';
import { ContextProvider } from 'teleport';
import {
AwsRdsDatabase,
integrationService,
} from 'teleport/services/integrations';
import { createTeleportContext } from 'teleport/mocks/contexts';
import cfg from 'teleport/config';
import TeleportContext from 'teleport/teleportContext';
import {
DiscoverContextState,
DiscoverProvider,
} from 'teleport/Discover/useDiscover';
import {
DatabaseEngine,
DatabaseLocation,
} from 'teleport/Discover/SelectResource';
import { FeaturesContextProvider } from 'teleport/FeaturesContext';
import { EnrollRdsDatabase } from './EnrollRdsDatabase';
describe('test EnrollRdsDatabase.tsx', () => {
const ctx = createTeleportContext();
const discoverCtx: DiscoverContextState = {
agentMeta: {} as any,
currentStep: 0,
nextStep: jest.fn(x => x),
prevStep: () => null,
onSelectResource: () => null,
resourceSpec: {
dbMeta: {
location: DatabaseLocation.Aws,
engine: DatabaseEngine.AuroraMysql,
},
} as any,
viewConfig: null,
indexedViews: [],
setResourceSpec: () => null,
updateAgentMeta: jest.fn(x => x),
emitErrorEvent: () => null,
emitEvent: () => null,
eventState: null,
};
beforeEach(() => {
jest
.spyOn(ctx.databaseService, 'fetchDatabases')
.mockResolvedValue({ agents: [] });
});
afterEach(() => {
jest.clearAllMocks();
});
test('without rds database result, does not attempt to fetch db servers', async () => {
renderRdsDatabase(ctx, discoverCtx);
jest
.spyOn(integrationService, 'fetchAwsRdsDatabases')
.mockResolvedValue({ databases: [] });
// select a region from selector.
const selectEl = screen.getByLabelText(/aws region/i);
fireEvent.focus(selectEl);
fireEvent.keyDown(selectEl, { key: 'ArrowDown', keyCode: 40 });
fireEvent.click(screen.getByText('us-east-2'));
// No results are rendered.
await screen.findByText(/no result/i);
expect(integrationService.fetchAwsRdsDatabases).toHaveBeenCalledTimes(1);
expect(ctx.databaseService.fetchDatabases).not.toHaveBeenCalled();
});
test('with rds database result, makes a fetch request for db servers', async () => {
renderRdsDatabase(ctx, discoverCtx);
jest.spyOn(integrationService, 'fetchAwsRdsDatabases').mockResolvedValue({
databases: mockAwsDbs,
});
// select a region from selector.
const selectEl = screen.getByLabelText(/aws region/i);
fireEvent.focus(selectEl);
fireEvent.keyDown(selectEl, { key: 'ArrowDown', keyCode: 40 });
fireEvent.click(screen.getByText('us-east-2'));
// Rds results renders result.
await screen.findByText(/rds-1/i);
expect(integrationService.fetchAwsRdsDatabases).toHaveBeenCalledTimes(1);
expect(ctx.databaseService.fetchDatabases).toHaveBeenCalledTimes(1);
});
});
function renderRdsDatabase(
ctx: TeleportContext,
discoverCtx: DiscoverContextState
) {
return render(
<MemoryRouter
initialEntries={[
{ pathname: cfg.routes.discover, state: { entity: 'database' } },
]}
>
<ContextProvider ctx={ctx}>
<FeaturesContextProvider value={[]}>
<DiscoverProvider mockCtx={discoverCtx}>
<EnrollRdsDatabase />
</DiscoverProvider>
</FeaturesContextProvider>
</ContextProvider>
</MemoryRouter>
);
}
const mockAwsDbs: AwsRdsDatabase[] = [
{
engine: 'postgres',
name: 'rds-1',
uri: 'endpoint-1',
status: 'available',
labels: [{ name: 'env', value: 'prod' }],
accountId: 'account-id-1',
resourceId: 'resource-id-1',
},
];

View file

@ -114,6 +114,13 @@ export function EnrollRdsDatabase() {
}
);
// Abort if there were no rds dbs for the selected region.
if (fetchedRdsDbs.length <= 0) {
setFetchDbAttempt({ status: 'success' });
setTableData({ ...data, fetchStatus: 'disabled' });
return;
}
// Check if fetched rds databases have a database
// server for it, to prevent user from enrolling
// the same db and getting an error from it.
@ -206,9 +213,6 @@ export function EnrollRdsDatabase() {
onRefresh={refreshDatabaseList}
clear={clear}
disableSelector={fetchDbAttempt.status === 'processing'}
disableFetch={
fetchDbAttempt.status === 'processing' || tableData.items.length > 0
}
/>
<DatabaseList
items={tableData.items}