KC: Make viewing/editing config a single view (#2613)

* Get rid of KC edit config page

* fix e2e-checks

Co-authored-by: VladSenyuta <vlad.senyuta@gmail.com>
This commit is contained in:
Oleg Shur 2022-09-26 12:05:01 +03:00 committed by GitHub
parent b940c28b5c
commit bae5c39cf2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 103 additions and 236 deletions

View file

@ -23,9 +23,9 @@ public class ConnectorsView {
return this;
}
@Step("Open 'Edit Config' of connector")
public ConnectorUpdateView openEditConfig() {
BrowserUtils.javaExecutorClick($x("//button[text()='Edit Config']"));
@Step()
public ConnectorUpdateView openConfigTab() {
BrowserUtils.javaExecutorClick($(By.xpath("//a[text() ='Config']")));
return new ConnectorUpdateView();
}

View file

@ -81,7 +81,7 @@ public class ConnectorsTests extends BaseTest {
.waitUntilScreenReady()
.openConnector(CONNECTOR_FOR_UPDATE.getName());
pages.connectorsView.connectorIsVisibleOnOverview();
pages.connectorsView.openEditConfig()
pages.connectorsView.openConfigTab()
.updConnectorConfig(CONNECTOR_FOR_UPDATE.getConfig());
pages.openConnectorsList(CLUSTER_NAME);
Assertions.assertTrue(pages.connectorsList.isConnectorVisible(CONNECTOR_FOR_UPDATE.getName()),"isConnectorVisible()");

View file

@ -2,7 +2,6 @@ import React from 'react';
import { Navigate, Routes, Route } from 'react-router-dom';
import {
RouteParams,
clusterConnectConnectorEditRelativePath,
clusterConnectConnectorRelativePath,
clusterConnectConnectorsRelativePath,
clusterConnectorNewRelativePath,
@ -13,7 +12,6 @@ import useAppParams from 'lib/hooks/useAppParams';
import ListPage from './List/ListPage';
import New from './New/New';
import Edit from './Edit/Edit';
import DetailsPage from './Details/DetailsPage';
const Connect: React.FC = () => {
@ -23,10 +21,6 @@ const Connect: React.FC = () => {
<Routes>
<Route index element={<ListPage />} />
<Route path={clusterConnectorNewRelativePath} element={<New />} />
<Route
path={clusterConnectConnectorEditRelativePath}
element={<Edit />}
/>
<Route
path={getNonExactPath(clusterConnectConnectorRelativePath)}
element={<DetailsPage />}

View file

@ -10,7 +10,6 @@ import {
useUpdateConnectorState,
} from 'lib/hooks/api/kafkaConnect';
import {
clusterConnectConnectorEditPath,
clusterConnectorsPath,
RouterParamsClusterConnectConnector,
} from 'lib/paths';
@ -115,20 +114,6 @@ const Actions: React.FC = () => {
>
Restart Failed Tasks
</Button>
<Button
buttonSize="M"
buttonType="primary"
type="button"
disabled={isMutating}
to={clusterConnectConnectorEditPath(
routerProps.clusterName,
routerProps.connectName,
routerProps.connectorName
)}
>
Edit Config
</Button>
<Button
buttonSize="M"
buttonType="secondary"

View file

@ -31,7 +31,6 @@ const expectActionButtonsExists = () => {
expect(screen.getByText('Restart Connector')).toBeInTheDocument();
expect(screen.getByText('Restart All Tasks')).toBeInTheDocument();
expect(screen.getByText('Restart Failed Tasks')).toBeInTheDocument();
expect(screen.getByText('Edit Config')).toBeInTheDocument();
expect(screen.getByText('Delete')).toBeInTheDocument();
};
@ -63,7 +62,7 @@ describe('Actions', () => {
data: set({ ...connector }, 'status.state', ConnectorState.PAUSED),
}));
renderComponent();
expect(screen.getAllByRole('button').length).toEqual(6);
expect(screen.getAllByRole('button').length).toEqual(5);
expect(screen.getByText('Resume')).toBeInTheDocument();
expect(screen.queryByText('Pause')).not.toBeInTheDocument();
expectActionButtonsExists();
@ -74,7 +73,7 @@ describe('Actions', () => {
data: set({ ...connector }, 'status.state', ConnectorState.FAILED),
}));
renderComponent();
expect(screen.getAllByRole('button').length).toEqual(5);
expect(screen.getAllByRole('button').length).toEqual(4);
expect(screen.queryByText('Resume')).not.toBeInTheDocument();
expect(screen.queryByText('Pause')).not.toBeInTheDocument();
expectActionButtonsExists();
@ -85,7 +84,7 @@ describe('Actions', () => {
data: set({ ...connector }, 'status.state', ConnectorState.UNASSIGNED),
}));
renderComponent();
expect(screen.getAllByRole('button').length).toEqual(5);
expect(screen.getAllByRole('button').length).toEqual(4);
expect(screen.queryByText('Resume')).not.toBeInTheDocument();
expect(screen.queryByText('Pause')).not.toBeInTheDocument();
expectActionButtonsExists();
@ -96,7 +95,7 @@ describe('Actions', () => {
data: set({ ...connector }, 'status.state', ConnectorState.RUNNING),
}));
renderComponent();
expect(screen.getAllByRole('button').length).toEqual(6);
expect(screen.getAllByRole('button').length).toEqual(5);
expect(screen.queryByText('Resume')).not.toBeInTheDocument();
expect(screen.getByText('Pause')).toBeInTheDocument();
expectActionButtonsExists();

View file

@ -1,23 +1,95 @@
import React from 'react';
import useAppParams from 'lib/hooks/useAppParams';
import Editor from 'components/common/Editor/Editor';
import { Controller, useForm } from 'react-hook-form';
import { ErrorMessage } from '@hookform/error-message';
import { yupResolver } from '@hookform/resolvers/yup';
import { RouterParamsClusterConnectConnector } from 'lib/paths';
import { useConnectorConfig } from 'lib/hooks/api/kafkaConnect';
import yup from 'lib/yupExtended';
import Editor from 'components/common/Editor/Editor';
import { Button } from 'components/common/Button/Button';
import {
useConnectorConfig,
useUpdateConnectorConfig,
} from 'lib/hooks/api/kafkaConnect';
import {
ConnectEditWarningMessageStyled,
ConnectEditWrapperStyled,
} from './Config.styled';
const validationSchema = yup.object().shape({
config: yup.string().required().isJsonObject(),
});
interface FormValues {
config: string;
}
const Config: React.FC = () => {
const routerProps = useAppParams<RouterParamsClusterConnectConnector>();
const { data: config } = useConnectorConfig(routerProps);
const routerParams = useAppParams<RouterParamsClusterConnectConnector>();
const { data: config } = useConnectorConfig(routerParams);
const mutation = useUpdateConnectorConfig(routerParams);
if (!config) return null;
const {
handleSubmit,
control,
reset,
formState: { isDirty, isSubmitting, isValid, errors },
setValue,
} = useForm<FormValues>({
mode: 'onTouched',
resolver: yupResolver(validationSchema),
defaultValues: {
config: JSON.stringify(config, null, '\t'),
},
});
React.useEffect(() => {
if (config) {
setValue('config', JSON.stringify(config, null, '\t'));
}
}, [config, setValue]);
const onSubmit = async (values: FormValues) => {
const requestBody = JSON.parse(values.config.trim());
await mutation.mutateAsync(requestBody);
reset(values);
};
const hasCredentials = JSON.stringify(config, null, '\t').includes(
'"******"'
);
return (
<Editor
readOnly
value={JSON.stringify(config, null, '\t')}
highlightActiveLine={false}
isFixedHeight
style={{ margin: '16px' }}
/>
<ConnectEditWrapperStyled>
{hasCredentials && (
<ConnectEditWarningMessageStyled>
Please replace ****** with the real credential values to avoid
accidentally breaking your connector config!
</ConnectEditWarningMessageStyled>
)}
<form onSubmit={handleSubmit(onSubmit)} aria-label="Edit connect form">
<div>
<Controller
control={control}
name="config"
render={({ field }) => (
<Editor {...field} readOnly={isSubmitting} />
)}
/>
</div>
<div>
<ErrorMessage errors={errors} name="config" />
</div>
<Button
buttonSize="M"
buttonType="primary"
type="submit"
disabled={!isValid || isSubmitting || !isDirty}
>
Submit
</Button>
</form>
</ConnectEditWrapperStyled>
);
};

View file

@ -1,46 +0,0 @@
import React from 'react';
import { render, WithRoute } from 'lib/testHelpers';
import { clusterConnectConnectorConfigPath } from 'lib/paths';
import Config from 'components/Connect/Details/Config/Config';
import { screen } from '@testing-library/dom';
import { useConnectorConfig } from 'lib/hooks/api/kafkaConnect';
import { connector } from 'lib/fixtures/kafkaConnect';
jest.mock('components/common/Editor/Editor', () => () => (
<div>mock-Editor</div>
));
jest.mock('lib/hooks/api/kafkaConnect', () => ({
useConnectorConfig: jest.fn(),
}));
describe('Config', () => {
const renderComponent = () =>
render(
<WithRoute path={clusterConnectConnectorConfigPath()}>
<Config />
</WithRoute>,
{
initialEntries: [
clusterConnectConnectorConfigPath(
'my-cluster',
'my-connect',
'my-connector'
),
],
}
);
it('is empty when no config', () => {
(useConnectorConfig as jest.Mock).mockImplementation(() => ({}));
renderComponent();
expect(screen.queryByText('mock-Editor')).not.toBeInTheDocument();
});
it('renders editor', () => {
(useConnectorConfig as jest.Mock).mockImplementation(() => ({
data: connector.config,
}));
renderComponent();
expect(screen.getByText('mock-Editor')).toBeInTheDocument();
});
});

View file

@ -1,10 +1,7 @@
import React from 'react';
import { render, WithRoute } from 'lib/testHelpers';
import {
clusterConnectConnectorConfigPath,
clusterConnectConnectorEditPath,
} from 'lib/paths';
import Edit from 'components/Connect/Edit/Edit';
import { clusterConnectConnectorConfigPath } from 'lib/paths';
import Config from 'components/Connect/Details/Config/Config';
import { connector } from 'lib/fixtures/kafkaConnect';
import { waitFor } from '@testing-library/dom';
import { act, fireEvent, screen } from '@testing-library/react';
@ -31,16 +28,16 @@ const [clusterName, connectName, connectorName] = [
'my-connector',
];
describe('Edit', () => {
const pathname = clusterConnectConnectorEditPath();
describe('Config', () => {
const pathname = clusterConnectConnectorConfigPath();
const renderComponent = () =>
render(
<WithRoute path={pathname}>
<Edit />
<Config />
</WithRoute>,
{
initialEntries: [
clusterConnectConnectorEditPath(
clusterConnectConnectorConfigPath(
clusterName,
connectName,
connectorName
@ -66,11 +63,6 @@ describe('Edit', () => {
renderComponent();
fireEvent.submit(screen.getByRole('form'));
await waitFor(() => expect(updateConfig).toHaveBeenCalledTimes(1));
await waitFor(() => expect(mockHistoryPush).toHaveBeenCalledTimes(1));
expect(mockHistoryPush).toHaveBeenCalledWith(
clusterConnectConnectorConfigPath(clusterName, connectName, connectorName)
);
});
it('does not redirect to connector config view on unsuccessful submit', async () => {

View file

@ -1,109 +0,0 @@
import React from 'react';
import { useNavigate } from 'react-router-dom';
import useAppParams from 'lib/hooks/useAppParams';
import { Controller, useForm } from 'react-hook-form';
import { ErrorMessage } from '@hookform/error-message';
import { yupResolver } from '@hookform/resolvers/yup';
import {
clusterConnectConnectorConfigPath,
RouterParamsClusterConnectConnector,
} from 'lib/paths';
import yup from 'lib/yupExtended';
import Editor from 'components/common/Editor/Editor';
import { Button } from 'components/common/Button/Button';
import {
useConnectorConfig,
useUpdateConnectorConfig,
} from 'lib/hooks/api/kafkaConnect';
import {
ConnectEditWarningMessageStyled,
ConnectEditWrapperStyled,
} from './Edit.styled';
const validationSchema = yup.object().shape({
config: yup.string().required().isJsonObject(),
});
interface FormValues {
config: string;
}
const Edit: React.FC = () => {
const routerParams = useAppParams<RouterParamsClusterConnectConnector>();
const navigate = useNavigate();
const { data: config } = useConnectorConfig(routerParams);
const mutation = useUpdateConnectorConfig(routerParams);
const {
handleSubmit,
control,
formState: { isDirty, isSubmitting, isValid, errors },
setValue,
} = useForm<FormValues>({
mode: 'onTouched',
resolver: yupResolver(validationSchema),
defaultValues: {
config: JSON.stringify(config, null, '\t'),
},
});
React.useEffect(() => {
if (config) {
setValue('config', JSON.stringify(config, null, '\t'));
}
}, [config, setValue]);
const onSubmit = async (values: FormValues) => {
const requestBody = JSON.parse(values.config.trim());
const connector = await mutation.mutateAsync(requestBody);
if (connector) {
navigate(
clusterConnectConnectorConfigPath(
routerParams.clusterName,
routerParams.connectName,
routerParams.connectorName
)
);
}
};
const hasCredentials = JSON.stringify(config, null, '\t').includes(
'"******"'
);
return (
<ConnectEditWrapperStyled>
{hasCredentials && (
<ConnectEditWarningMessageStyled>
Please replace ****** with the real credential values to avoid
accidentally breaking your connector config!
</ConnectEditWarningMessageStyled>
)}
<form onSubmit={handleSubmit(onSubmit)} aria-label="Edit connect form">
<div>
<Controller
control={control}
name="config"
render={({ field }) => (
<Editor {...field} readOnly={isSubmitting} />
)}
/>
</div>
<div>
<ErrorMessage errors={errors} name="config" />
</div>
<Button
buttonSize="M"
buttonType="primary"
type="submit"
disabled={!isValid || isSubmitting || !isDirty}
>
Submit
</Button>
</form>
</ConnectEditWrapperStyled>
);
};
export default Edit;

View file

@ -2,12 +2,10 @@ import React from 'react';
import { render, WithRoute } from 'lib/testHelpers';
import { screen } from '@testing-library/react';
import Connect from 'components/Connect/Connect';
import { store } from 'redux/store';
import {
clusterConnectorsPath,
clusterConnectorNewPath,
clusterConnectConnectorPath,
clusterConnectConnectorEditPath,
getNonExactPath,
clusterConnectsPath,
} from 'lib/paths';
@ -16,7 +14,6 @@ const ConnectCompText = {
new: 'New Page',
list: 'List Page',
details: 'Details Page',
edit: 'Edit Page',
};
jest.mock('components/Connect/New/New', () => () => (
@ -28,9 +25,6 @@ jest.mock('components/Connect/List/ListPage', () => () => (
jest.mock('components/Connect/Details/DetailsPage', () => () => (
<div>{ConnectCompText.details}</div>
));
jest.mock('components/Connect/Edit/Edit', () => () => (
<div>{ConnectCompText.edit}</div>
));
describe('Connect', () => {
const renderComponent = (pathname: string, routePath: string) =>
@ -38,7 +32,7 @@ describe('Connect', () => {
<WithRoute path={getNonExactPath(routePath)}>
<Connect />
</WithRoute>,
{ initialEntries: [pathname], store }
{ initialEntries: [pathname] }
);
it('renders ListPage', () => {
@ -64,16 +58,4 @@ describe('Connect', () => {
);
expect(screen.getByText(ConnectCompText.details)).toBeInTheDocument();
});
it('renders EditContainer', () => {
renderComponent(
clusterConnectConnectorEditPath(
'my-cluster',
'my-connect',
'my-connector'
),
clusterConnectsPath()
);
expect(screen.getByText(ConnectCompText.edit)).toBeInTheDocument();
});
});

View file

@ -8,6 +8,7 @@ import { kafkaConnectApiClient as api } from 'lib/api';
import sortBy from 'lodash/sortBy';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { ClusterName } from 'redux/interfaces';
import { showSuccessAlert } from 'lib/errorHandling';
interface UseConnectorProps {
clusterName: ClusterName;
@ -43,10 +44,6 @@ const connectorTasksKey = (props: UseConnectorProps) => [
...connectorKey(props),
'tasks',
];
const connectorConfigKey = (props: UseConnectorProps) => [
...connectorKey(props),
'config',
];
export function useConnects(clusterName: ClusterName) {
return useQuery(connectsKey(clusterName), () =>
@ -104,8 +101,10 @@ export function useUpdateConnectorConfig(props: UseConnectorProps) {
api.setConnectorConfig({ ...props, requestBody }),
{
onSuccess: () => {
showSuccessAlert({
message: `Config successfully updated.`,
});
client.invalidateQueries(connectorKey(props));
client.invalidateQueries(connectorConfigKey(props));
},
}
);

View file

@ -201,7 +201,6 @@ export const clusterConnectorsRelativePath = 'connectors';
export const clusterConnectorNewRelativePath = 'create-new';
export const clusterConnectConnectorsRelativePath = `${RouteParams.connectName}/connectors`;
export const clusterConnectConnectorRelativePath = `${clusterConnectConnectorsRelativePath}/${RouteParams.connectorName}`;
export const clusterConnectConnectorEditRelativePath = `${clusterConnectConnectorRelativePath}/edit`;
export const clusterConnectConnectorTasksRelativePath = 'tasks';
export const clusterConnectConnectorConfigRelativePath = 'config';