Added logic to toggle auto-connect on servers

This commit is contained in:
Alejandro Celaya 2021-10-22 20:13:23 +02:00
parent ada5488a6c
commit 7637ce3107
6 changed files with 132 additions and 23 deletions

View file

@ -57,11 +57,11 @@ export const ManageServers = (
</div>
</Row>
<SimpleCard title="Shlink servers">
<SimpleCard>
<table className="table table-hover mb-0">
<thead className="responsive-table__header">
<tr>
{hasAutoConnect && <th />}
{hasAutoConnect && <th style={{ width: '50px' }} />}
<th>Name</th>
<th>Base URL</th>
<th />

View file

@ -7,9 +7,9 @@ import {
faEdit as editIcon,
faMinusCircle as deleteIcon,
faPlug as connectIcon,
faToggleOn as toggleOnIcon,
faToggleOff as toggleOffIcon,
faBan as toggleOffIcon,
} from '@fortawesome/free-solid-svg-icons';
import { faCircle as toggleOnIcon } from '@fortawesome/free-regular-svg-icons';
import { DropdownBtnMenu } from '../utils/DropdownBtnMenu';
import { useToggle } from '../utils/helpers/hooks';
import { ServerWithId } from './data';
@ -20,23 +20,29 @@ export interface ManageServersRowProps {
hasAutoConnect: boolean;
}
interface ManageServersRowPropsConnectProps extends ManageServersRowProps {
setAutoConnect: (server: ServerWithId, autoConnect: boolean) => void;
}
export const ManageServersRow = (
DeleteServerModal: FC<DeleteServerModalProps>,
): FC<ManageServersRowProps> => ({ server, hasAutoConnect }) => {
): FC<ManageServersRowPropsConnectProps> => ({ server, hasAutoConnect, setAutoConnect }) => {
const [ isMenuOpen, toggleMenu ] = useToggle();
const [ isModalOpen,, showModal, hideModal ] = useToggle();
const serverUrl = `/server/${server.id}`;
const { autoConnect: isAutoConnect } = server;
const autoConnectIcon = isAutoConnect ? toggleOnIcon : toggleOffIcon;
const autoConnectIcon = isAutoConnect ? toggleOffIcon : toggleOnIcon;
return (
<tr className="responsive-table__row">
{hasAutoConnect && (
<td className="responsive-table__cell text-lg-right" data-th="Auto-connect">
<td className="responsive-table__cell" data-th="Auto-connect">
{isAutoConnect && (
<>
<FontAwesomeIcon icon={checkIcon} className="text-primary" id="autoConnectIcon" />
<UncontrolledTooltip target="autoConnectIcon">Auto-connect to this server</UncontrolledTooltip>
<UncontrolledTooltip target="autoConnectIcon" placement="right">
Auto-connect to this server
</UncontrolledTooltip>
</>
)}
</td>
@ -53,7 +59,7 @@ export const ManageServersRow = (
<DropdownItem tag={Link} to={`${serverUrl}/edit`}>
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit server
</DropdownItem>
<DropdownItem>
<DropdownItem onClick={() => setAutoConnect(server, !server.autoConnect)}>
<FontAwesomeIcon icon={autoConnectIcon} fixedWidth /> {isAutoConnect ? 'Do not a' : 'A'}uto-connect
</DropdownItem>
<DropdownItem divider />

View file

@ -3,7 +3,7 @@ import { DropdownItem, DropdownMenu, DropdownToggle, UncontrolledDropdown } from
import { Link } from 'react-router-dom';
import { faPlus as plusIcon, faServer as serverIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { isServerWithId, SelectedServer, ServersMap } from './data';
import { getServerId, SelectedServer, ServersMap } from './data';
export interface ServersDropdownProps {
servers: ServersMap;
@ -25,12 +25,7 @@ const ServersDropdown = ({ servers, selectedServer }: ServersDropdownProps) => {
return (
<>
{serversList.map(({ name, id }) => (
<DropdownItem
key={id}
tag={Link}
to={`/server/${id}`}
active={isServerWithId(selectedServer) && selectedServer.id === id}
>
<DropdownItem key={id} tag={Link} to={`/server/${id}`} active={getServerId(selectedServer) === id}>
{name}
</DropdownItem>
))}

View file

@ -1,4 +1,4 @@
import { assoc, dissoc, map, pipe, reduce } from 'ramda';
import { assoc, dissoc, fromPairs, map, pipe, reduce, toPairs } from 'ramda';
import { v4 as uuid } from 'uuid';
import { Action } from 'redux';
import { ServerData, ServersMap, ServerWithId } from '../data';
@ -8,12 +8,22 @@ import { buildReducer } from '../../utils/helpers/redux';
export const EDIT_SERVER = 'shlink/servers/EDIT_SERVER';
export const DELETE_SERVER = 'shlink/servers/DELETE_SERVER';
export const CREATE_SERVERS = 'shlink/servers/CREATE_SERVERS';
export const SET_AUTO_CONNECT = 'shlink/servers/SET_AUTO_CONNECT';
/* eslint-enable padding-line-between-statements */
export interface CreateServersAction extends Action<string> {
newServers: ServersMap;
}
interface DeleteServerAction extends Action<string> {
serverId: string;
}
interface SetAutoConnectAction extends Action<string> {
serverId: string;
autoConnect: boolean;
}
const initialState: ServersMap = {};
const serverWithId = (server: ServerWithId | ServerData): ServerWithId => {
@ -24,12 +34,28 @@ const serverWithId = (server: ServerWithId | ServerData): ServerWithId => {
return assoc('id', uuid(), server);
};
export default buildReducer<ServersMap, CreateServersAction>({
export default buildReducer<ServersMap, CreateServersAction & DeleteServerAction & SetAutoConnectAction>({
[CREATE_SERVERS]: (state, { newServers }) => ({ ...state, ...newServers }),
[DELETE_SERVER]: (state, { serverId }: any) => dissoc(serverId, state),
[DELETE_SERVER]: (state, { serverId }) => dissoc(serverId, state),
[EDIT_SERVER]: (state, { serverId, serverData }: any) => !state[serverId]
? state
: assoc(serverId, { ...state[serverId], ...serverData }, state),
[SET_AUTO_CONNECT]: (state, { serverId, autoConnect }) => {
if (!state[serverId]) {
return state;
}
if (!autoConnect) {
return assoc(serverId, { ...state[serverId], autoConnect }, state);
}
return fromPairs(
toPairs(state).map(([ evaluatedServerId, server ]) => [
evaluatedServerId,
{ ...server, autoConnect: evaluatedServerId === serverId },
]),
);
},
}, initialState);
const serversListToMap = reduce<ServerWithId, ServersMap>((acc, server) => assoc(server.id, server, acc), {});
@ -48,4 +74,10 @@ export const editServer = (serverId: string, serverData: Partial<ServerData>) =>
serverData,
});
export const deleteServer = ({ id }: ServerWithId) => ({ type: DELETE_SERVER, serverId: id });
export const deleteServer = ({ id }: ServerWithId): DeleteServerAction => ({ type: DELETE_SERVER, serverId: id });
export const setAutoConnect = ({ id }: ServerWithId, autoConnect: boolean): SetAutoConnectAction => ({
type: SET_AUTO_CONNECT,
serverId: id,
autoConnect,
});

View file

@ -7,7 +7,7 @@ import DeleteServerButton from '../DeleteServerButton';
import { EditServer } from '../EditServer';
import ImportServersBtn from '../helpers/ImportServersBtn';
import { resetSelectedServer, selectServer } from '../reducers/selectedServer';
import { createServer, createServers, deleteServer, editServer } from '../reducers/servers';
import { createServer, createServers, deleteServer, editServer, setAutoConnect } from '../reducers/servers';
import { fetchServers } from '../reducers/remoteServers';
import ForServerVersion from '../helpers/ForServerVersion';
import { ServerError } from '../helpers/ServerError';
@ -32,7 +32,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter:
bottle.decorator('ManageServers', connect([ 'servers' ]));
bottle.serviceFactory('ManageServersRow', ManageServersRow, 'DeleteServerModal');
bottle.decorator('ManageServers', connect([ 'servers' ]));
bottle.decorator('ManageServersRow', connect(null, [ 'setAutoConnect' ]));
bottle.serviceFactory('CreateServer', CreateServer, 'ImportServersBtn', 'useStateFlagTimeout');
bottle.decorator('CreateServer', withoutSelectedServer);
@ -77,6 +77,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter:
bottle.serviceFactory('createServers', () => createServers);
bottle.serviceFactory('deleteServer', () => deleteServer);
bottle.serviceFactory('editServer', () => editServer);
bottle.serviceFactory('setAutoConnect', () => setAutoConnect);
bottle.serviceFactory('fetchServers', fetchServers, 'axios');
bottle.serviceFactory('resetSelectedServer', () => resetSelectedServer);

View file

@ -1,13 +1,15 @@
import { values } from 'ramda';
import { dissoc, values } from 'ramda';
import { Mock } from 'ts-mockery';
import reducer, {
createServer,
deleteServer,
createServers,
editServer,
setAutoConnect,
EDIT_SERVER,
DELETE_SERVER,
CREATE_SERVERS,
SET_AUTO_CONNECT,
} from '../../../src/servers/reducers/servers';
import { RegularServer } from '../../../src/servers/data';
@ -29,6 +31,15 @@ describe('serverReducer', () => {
def456: { id: 'def456' },
}));
it('returns as it is when action is EDIT_SERVER and server does not exist', () =>
expect(reducer(
list,
{ type: EDIT_SERVER, serverId: 'invalid', serverData: { foo: 'foo' } } as any,
)).toEqual({
abc123: { id: 'abc123' },
def456: { id: 'def456' },
}));
it('removes server when action is DELETE_SERVER', () =>
expect(reducer(list, { type: DELETE_SERVER, serverId: 'abc123' } as any)).toEqual({
def456: { id: 'def456' },
@ -45,6 +56,51 @@ describe('serverReducer', () => {
def456: { id: 'def456' },
ghi789: { id: 'ghi789' },
}));
it.each([
[ true ],
[ false ],
])('returns state as it is when trying to set auto-connect on invalid server', (autoConnect) =>
expect(reducer(list, {
type: SET_AUTO_CONNECT,
serverId: 'invalid',
autoConnect,
} as any)).toEqual({
abc123: { id: 'abc123' },
def456: { id: 'def456' },
}));
it('disables auto-connect on a server which is already set to auto-connect', () => {
const listWithDisabledAutoConnect = {
...list,
abc123: { ...list.abc123, autoConnect: true },
};
expect(reducer(listWithDisabledAutoConnect, {
type: SET_AUTO_CONNECT,
serverId: 'abc123',
autoConnect: false,
} as any)).toEqual({
abc123: { id: 'abc123', autoConnect: false },
def456: { id: 'def456' },
});
});
it('disables auto-connect on all servers except selected one', () => {
const listWithEnabledAutoConnect = {
...list,
abc123: { ...list.abc123, autoConnect: true },
};
expect(reducer(listWithEnabledAutoConnect, {
type: SET_AUTO_CONNECT,
serverId: 'def456',
autoConnect: true,
} as any)).toEqual({
abc123: { id: 'abc123', autoConnect: false },
def456: { id: 'def456', autoConnect: true },
});
});
});
describe('action creators', () => {
@ -82,6 +138,25 @@ describe('serverReducer', () => {
expect(result).toEqual(expect.objectContaining({ type: CREATE_SERVERS }));
});
it('generates an id for every provided server if they do not have it', () => {
const servers = values(list).map(dissoc('id'));
const { newServers } = createServers(servers);
expect(values(newServers).every(({ id }) => !!id)).toEqual(true);
});
});
describe('setAutoConnect', () => {
it.each([
[ true ],
[ false ],
])('returns expected action', (autoConnect) => {
const serverToEdit = Mock.of<RegularServer>({ id: 'abc123' });
const result = setAutoConnect(serverToEdit, autoConnect);
expect(result).toEqual({ type: SET_AUTO_CONNECT, serverId: 'abc123', autoConnect });
});
});
});
});