Added logic to detect duplicated servers when importing a servers list

This commit is contained in:
Alejandro Celaya
2022-01-01 12:20:09 +01:00
parent 3cb066f5f5
commit 98398a048b
10 changed files with 128 additions and 73 deletions

View File

@@ -1,31 +0,0 @@
import { FC } from 'react';
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { ServerData } from '../data';
interface DuplicatedServerModalProps {
serverData?: ServerData;
isOpen: boolean;
toggle: () => void;
onDiscard: () => void;
onSave: () => void;
}
export const DuplicatedServerModal: FC<DuplicatedServerModalProps> = (
{ isOpen, toggle, serverData, onDiscard, onSave },
) => (
<Modal centered isOpen={isOpen} toggle={toggle}>
<ModalHeader>Duplicated server</ModalHeader>
<ModalBody>
<p>There is already a server with:</p>
<ul>
<li>URL: <b>{serverData?.url}</b></li>
<li>API key: <b>{serverData?.apiKey}</b></li>
</ul>
Do you want to save this server anyway?
</ModalBody>
<ModalFooter>
<Button color="link" onClick={onDiscard}>Discard</Button>
<Button color="primary" onClick={onSave}>Save anyway</Button>
</ModalFooter>
</Modal>
);

View File

@@ -0,0 +1,38 @@
import { FC, Fragment } from 'react';
import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import { ServerData } from '../data';
interface DuplicatedServersModalProps {
duplicatedServers: ServerData[];
isOpen: boolean;
onDiscard: () => void;
onSave: () => void;
}
export const DuplicatedServersModal: FC<DuplicatedServersModalProps> = (
{ isOpen, duplicatedServers, onDiscard, onSave },
) => {
const hasMultipleServers = duplicatedServers.length > 1;
return (
<Modal centered isOpen={isOpen}>
<ModalHeader>Duplicated server{hasMultipleServers && 's'}</ModalHeader>
<ModalBody>
<p>{hasMultipleServers ? 'The next servers already exist:' : 'There is already a server with:'}</p>
<ul>
{duplicatedServers.map(({ url, apiKey }, index) => !hasMultipleServers ? (
<Fragment key={index}>
<li>URL: <b>{url}</b></li>
<li>API key: <b>{apiKey}</b></li>
</Fragment>
) : <li key={index}><b>{url}</b> - <b>{apiKey}</b></li>)}
</ul>
{hasMultipleServers ? 'Do you want to ignore duplicated servers?' : 'Do you want to save this server anyway?'}
</ModalBody>
<ModalFooter>
<Button color="link" onClick={onDiscard}>{hasMultipleServers ? 'Ignore duplicated' : 'Discard'}</Button>
<Button color="primary" onClick={onSave}>Save anyway</Button>
</ModalFooter>
</Modal>
);
};

View File

@@ -1,10 +1,12 @@
import { useRef, RefObject, ChangeEvent, MutableRefObject, FC } from 'react';
import { useRef, RefObject, ChangeEvent, MutableRefObject, FC, useState, useEffect } from 'react';
import { Button, UncontrolledTooltip } from 'reactstrap';
import { pipe } from 'ramda';
import { complement, pipe } from 'ramda';
import { faFileUpload as importIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import ServersImporter from '../services/ServersImporter';
import { ServerData } from '../data';
import { useToggle } from '../../utils/helpers/hooks';
import { ServersImporter } from '../services/ServersImporter';
import { ServerData, ServersMap } from '../data';
import { DuplicatedServersModal } from './DuplicatedServersModal';
import './ImportServersBtn.scss';
type Ref<T> = RefObject<T> | MutableRefObject<T>;
@@ -18,11 +20,16 @@ export interface ImportServersBtnProps {
interface ImportServersBtnConnectProps extends ImportServersBtnProps {
createServers: (servers: ServerData[]) => void;
servers: ServersMap;
fileRef: Ref<HTMLInputElement>;
}
const serversFiltering = (servers: ServerData[]) =>
({ url, apiKey }: ServerData) => servers.some((server) => server.url === url && server.apiKey === apiKey);
const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC<ImportServersBtnConnectProps> => ({
createServers,
servers,
fileRef,
children,
onImport = () => {},
@@ -31,15 +38,37 @@ const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC<Import
className = '',
}) => {
const ref = fileRef ?? useRef<HTMLInputElement>();
const onChange = async ({ target }: ChangeEvent<HTMLInputElement>) =>
const [ serversToCreate, setServersToCreate ] = useState<ServerData[] | undefined>();
const [ duplicatedServers, setDuplicatedServers ] = useState<ServerData[]>([]);
const [ isModalOpen,, showModal, hideModal ] = useToggle();
const create = pipe(createServers, onImport);
const createAllServers = pipe(() => create(serversToCreate ?? []), hideModal);
const createNonDuplicatedServers = pipe(
() => create((serversToCreate ?? []).filter(complement(serversFiltering(duplicatedServers)))),
hideModal,
);
const onFile = async ({ target }: ChangeEvent<HTMLInputElement>) =>
importServersFromFile(target.files?.[0])
.then(pipe(createServers, onImport))
.then(setServersToCreate)
.then(() => {
// Reset input after processing file
(target as { value: string | null }).value = null;
})
.catch(onImportError);
useEffect(() => {
if (!serversToCreate) {
return;
}
const existingServers = Object.values(servers);
const duplicatedServers = serversToCreate.filter(serversFiltering(existingServers));
const hasDuplicatedServers = !!duplicatedServers.length;
!hasDuplicatedServers ? create(serversToCreate) : setDuplicatedServers(duplicatedServers);
hasDuplicatedServers && showModal();
}, [ serversToCreate ]);
return (
<>
<Button outline id="importBtn" className={className} onClick={() => ref.current?.click()}>
@@ -49,7 +78,14 @@ const ImportServersBtn = ({ importServersFromFile }: ServersImporter): FC<Import
You can create servers by importing a CSV file with columns <b>name</b>, <b>apiKey</b> and <b>url</b>.
</UncontrolledTooltip>
<input type="file" accept="text/csv" className="import-servers-btn__csv-select" ref={ref} onChange={onChange} />
<input type="file" accept="text/csv" className="import-servers-btn__csv-select" ref={ref} onChange={onFile} />
<DuplicatedServersModal
isOpen={isModalOpen}
duplicatedServers={duplicatedServers}
onDiscard={createNonDuplicatedServers}
onSave={createAllServers}
/>
</>
);
};