Improvements on ManageServers

This commit is contained in:
Alejandro Celaya
2021-10-22 18:53:00 +02:00
parent 7f4263966e
commit 478209f50d
7 changed files with 52 additions and 26 deletions

View File

@@ -27,7 +27,7 @@ const ImportResult = ({ type }: { type: 'error' | 'success' }) => (
); );
const CreateServer = (ImportServersBtn: FC<ImportServersBtnProps>, useStateFlagTimeout: StateFlagTimeout) => ( const CreateServer = (ImportServersBtn: FC<ImportServersBtnProps>, useStateFlagTimeout: StateFlagTimeout) => (
{ servers, createServer, history: { push } }: CreateServerProps, { servers, createServer, history: { push, goBack } }: CreateServerProps,
) => { ) => {
const hasServers = !!Object.keys(servers).length; const hasServers = !!Object.keys(servers).length;
const [ serversImported, setServersImported ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME); const [ serversImported, setServersImported ] = useStateFlagTimeout(false, SHOW_IMPORT_MSG_TIME);
@@ -44,6 +44,7 @@ const CreateServer = (ImportServersBtn: FC<ImportServersBtnProps>, useStateFlagT
<ServerForm title={<h5 className="mb-0">Add new server</h5>} onSubmit={handleSubmit}> <ServerForm title={<h5 className="mb-0">Add new server</h5>} onSubmit={handleSubmit}>
{!hasServers && {!hasServers &&
<ImportServersBtn tooltipPlacement="top" onImport={setServersImported} onImportError={setErrorImporting} />} <ImportServersBtn tooltipPlacement="top" onImport={setServersImported} onImportError={setErrorImporting} />}
{hasServers && <Button outline onClick={goBack}>Cancel</Button>}
<Button outline color="primary" className="ml-2">Create server</Button> <Button outline color="primary" className="ml-2">Create server</Button>
</ServerForm> </ServerForm>

View File

@@ -10,7 +10,7 @@ interface EditServerProps {
} }
export const EditServer = (ServerError: FC) => withSelectedServer<EditServerProps>(( export const EditServer = (ServerError: FC) => withSelectedServer<EditServerProps>((
{ editServer, selectedServer, history: { push, goBack } }, { editServer, selectedServer, history: { goBack } },
) => { ) => {
if (!isServerWithId(selectedServer)) { if (!isServerWithId(selectedServer)) {
return null; return null;
@@ -18,7 +18,7 @@ export const EditServer = (ServerError: FC) => withSelectedServer<EditServerProp
const handleSubmit = (serverData: ServerData) => { const handleSubmit = (serverData: ServerData) => {
editServer(selectedServer.id, serverData); editServer(selectedServer.id, serverData);
push(`/server/${selectedServer.id}`); goBack();
}; };
return ( return (

View File

@@ -1,5 +1,5 @@
import { FC, useEffect, useState } from 'react'; import { FC, useEffect, useState } from 'react';
import { Button } from 'reactstrap'; import { Button, Row } from 'reactstrap';
import { faFileDownload as exportIcon, faPlus as plusIcon } from '@fortawesome/free-solid-svg-icons'; import { faFileDownload as exportIcon, faPlus as plusIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
@@ -41,15 +41,19 @@ export const ManageServers = (
<NoMenuLayout> <NoMenuLayout>
<SearchField className="mb-3" onChange={filterServers} /> <SearchField className="mb-3" onChange={filterServers} />
<div className="mb-3 d-flex flex-row"> <Row className="mb-3">
<ImportServersBtn onImportError={setErrorImporting} /> <div className="col-md-6 d-flex d-md-block mb-2 mb-md-0">
<Button outline className="ml-2" onClick={async () => serversExporter.exportServers()}> <ImportServersBtn className="flex-fill" onImportError={setErrorImporting} />
<FontAwesomeIcon icon={exportIcon} fixedWidth /> Export servers <Button outline className="ml-2 flex-fill" onClick={async () => serversExporter.exportServers()}>
</Button> <FontAwesomeIcon icon={exportIcon} fixedWidth /> Export servers
<Button outline color="primary" className="ml-auto" tag={Link} to="/server/create"> </Button>
<FontAwesomeIcon icon={plusIcon} fixedWidth /> Add a server </div>
</Button> <div className="col-md-6 text-md-right d-flex d-md-block">
</div> <Button outline color="primary" className="flex-fill" tag={Link} to="/server/create">
<FontAwesomeIcon icon={plusIcon} fixedWidth /> Add a server
</Button>
</div>
</Row>
<SimpleCard title="Shlink servers"> <SimpleCard title="Shlink servers">
<table className="table table-hover mb-0"> <table className="table table-hover mb-0">

View File

@@ -54,7 +54,7 @@ export const ManageServersRow = (
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit server <FontAwesomeIcon icon={editIcon} fixedWidth /> Edit server
</DropdownItem> </DropdownItem>
<DropdownItem> <DropdownItem>
<FontAwesomeIcon icon={autoConnectIcon} fixedWidth /> {isAutoConnect ? 'Unset' : 'Set'} auto-connect <FontAwesomeIcon icon={autoConnectIcon} fixedWidth /> {isAutoConnect ? 'Do not a' : 'A'}uto-connect
</DropdownItem> </DropdownItem>
<DropdownItem divider /> <DropdownItem divider />
<DropdownItem className="dropdown-item--danger" onClick={showModal}> <DropdownItem className="dropdown-item--danger" onClick={showModal}>

View File

@@ -12,6 +12,7 @@ export interface ImportServersBtnProps {
onImport?: () => void; onImport?: () => void;
onImportError?: (error: Error) => void; onImportError?: (error: Error) => void;
tooltipPlacement?: 'top' | 'bottom'; tooltipPlacement?: 'top' | 'bottom';
className?: string;
} }
interface ImportServersBtnConnectProps extends ImportServersBtnProps { interface ImportServersBtnConnectProps extends ImportServersBtnProps {
@@ -25,6 +26,7 @@ const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => ({
onImport = () => {}, onImport = () => {},
onImportError = () => {}, onImportError = () => {},
tooltipPlacement = 'bottom', tooltipPlacement = 'bottom',
className = '',
}: ImportServersBtnConnectProps) => { }: ImportServersBtnConnectProps) => {
const ref = fileRef ?? useRef<HTMLInputElement>(); const ref = fileRef ?? useRef<HTMLInputElement>();
const onChange = async ({ target }: ChangeEvent<HTMLInputElement>) => const onChange = async ({ target }: ChangeEvent<HTMLInputElement>) =>
@@ -39,7 +41,7 @@ const ImportServersBtn = ({ importServersFromFile }: ServersImporter) => ({
return ( return (
<> <>
<Button outline id="importBtn" onClick={() => ref.current?.click()}> <Button outline id="importBtn" className={className} onClick={() => ref.current?.click()}>
<FontAwesomeIcon icon={importIcon} fixedWidth /> Import from file <FontAwesomeIcon icon={importIcon} fixedWidth /> Import from file
</Button> </Button>
<UncontrolledTooltip placement={tooltipPlacement} target="importBtn"> <UncontrolledTooltip placement={tooltipPlacement} target="importBtn">

View File

@@ -10,8 +10,8 @@ describe('<EditServer />', () => {
let wrapper: ReactWrapper; let wrapper: ReactWrapper;
const ServerError = jest.fn(); const ServerError = jest.fn();
const editServerMock = jest.fn(); const editServerMock = jest.fn();
const push = jest.fn(); const goBack = jest.fn();
const historyMock = Mock.of<History>({ push }); const historyMock = Mock.of<History>({ goBack });
const match = Mock.of<match<{ serverId: string }>>({ const match = Mock.of<match<{ serverId: string }>>({
params: { serverId: 'abc123' }, params: { serverId: 'abc123' },
}); });
@@ -50,6 +50,6 @@ describe('<EditServer />', () => {
form.simulate('submit', {}); form.simulate('submit', {});
expect(editServerMock).toHaveBeenCalledTimes(1); expect(editServerMock).toHaveBeenCalledTimes(1);
expect(push).toHaveBeenCalledTimes(1); expect(goBack).toHaveBeenCalledTimes(1);
}); });
}); });

View File

@@ -15,25 +15,43 @@ describe('<ImportServersBtn />', () => {
const fileRef = { const fileRef = {
current: Mock.of<HTMLInputElement>({ click }), current: Mock.of<HTMLInputElement>({ click }),
}; };
const ImportServersBtn = importServersBtnConstruct(serversImporterMock);
beforeEach(() => { const createWrapper = (className?: string) => {
jest.clearAllMocks();
const ImportServersBtn = importServersBtnConstruct(serversImporterMock);
wrapper = shallow( wrapper = shallow(
<ImportServersBtn createServers={createServersMock} fileRef={fileRef} onImport={onImportMock} />, <ImportServersBtn
createServers={createServersMock}
className={className}
fileRef={fileRef}
onImport={onImportMock}
/>,
); );
});
return wrapper;
};
afterEach(jest.clearAllMocks);
afterEach(() => wrapper.unmount()); afterEach(() => wrapper.unmount());
it('renders a button, a tooltip and a file input', () => { it('renders a button, a tooltip and a file input', () => {
const wrapper = createWrapper();
expect(wrapper.find('#importBtn')).toHaveLength(1); expect(wrapper.find('#importBtn')).toHaveLength(1);
expect(wrapper.find(UncontrolledTooltip)).toHaveLength(1); expect(wrapper.find(UncontrolledTooltip)).toHaveLength(1);
expect(wrapper.find('.import-servers-btn__csv-select')).toHaveLength(1); expect(wrapper.find('.import-servers-btn__csv-select')).toHaveLength(1);
}); });
it.each([
[ undefined, '' ],
[ 'foo', 'foo' ],
[ 'bar', 'bar' ],
])('allows a class name to be provided', (providedClassName, expectedClassName) => {
const wrapper = createWrapper(providedClassName);
expect(wrapper.find('#importBtn').prop('className')).toEqual(expectedClassName);
});
it('triggers click on file ref when button is clicked', () => { it('triggers click on file ref when button is clicked', () => {
const wrapper = createWrapper();
const btn = wrapper.find('#importBtn'); const btn = wrapper.find('#importBtn');
btn.simulate('click'); btn.simulate('click');
@@ -42,6 +60,7 @@ describe('<ImportServersBtn />', () => {
}); });
it('imports servers when file input changes', (done) => { it('imports servers when file input changes', (done) => {
const wrapper = createWrapper();
const file = wrapper.find('.import-servers-btn__csv-select'); const file = wrapper.find('.import-servers-btn__csv-select');
file.simulate('change', { target: { files: [ '' ] } }); file.simulate('change', { target: { files: [ '' ] } });