Added logic in ManageDomains and DomainRow components to check if the domains status

This commit is contained in:
Alejandro Celaya
2021-12-26 13:53:17 +01:00
parent c05c74f009
commit a78467065a
5 changed files with 59 additions and 26 deletions

View File

@@ -1,22 +1,26 @@
import { FC } from 'react'; import { FC, useEffect } from 'react';
import { Button, UncontrolledTooltip } from 'reactstrap'; import { Button, UncontrolledTooltip } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { import {
faBan as forbiddenIcon, faBan as forbiddenIcon,
faCheck as defaultDomainIcon, faDotCircle as defaultDomainIcon,
faCheck as checkIcon,
faCircleNotch as loadingStatusIcon,
faEdit as editIcon, faEdit as editIcon,
} from '@fortawesome/free-solid-svg-icons'; } from '@fortawesome/free-solid-svg-icons';
import { ShlinkDomain, ShlinkDomainRedirects } from '../api/types'; import { ShlinkDomainRedirects } from '../api/types';
import { useToggle } from '../utils/helpers/hooks'; import { useToggle } from '../utils/helpers/hooks';
import { OptionalString } from '../utils/utils'; import { OptionalString } from '../utils/utils';
import { SelectedServer } from '../servers/data'; import { SelectedServer } from '../servers/data';
import { supportsDefaultDomainRedirectsEdition } from '../utils/helpers/features'; import { supportsDefaultDomainRedirectsEdition } from '../utils/helpers/features';
import { EditDomainRedirectsModal } from './helpers/EditDomainRedirectsModal'; import { EditDomainRedirectsModal } from './helpers/EditDomainRedirectsModal';
import { Domain, DomainStatus } from './data';
interface DomainRowProps { interface DomainRowProps {
domain: ShlinkDomain; domain: Domain;
defaultRedirects?: ShlinkDomainRedirects; defaultRedirects?: ShlinkDomainRedirects;
editDomainRedirects: (domain: string, redirects: Partial<ShlinkDomainRedirects>) => Promise<void>; editDomainRedirects: (domain: string, redirects: Partial<ShlinkDomainRedirects>) => Promise<void>;
checkDomainHealth: (domain: string) => void;
selectedServer: SelectedServer; selectedServer: SelectedServer;
} }
@@ -32,12 +36,27 @@ const DefaultDomain: FC = () => (
<UncontrolledTooltip target="defaultDomainIcon" placement="right">Default domain</UncontrolledTooltip> <UncontrolledTooltip target="defaultDomainIcon" placement="right">Default domain</UncontrolledTooltip>
</> </>
); );
const StatusIcon: FC<{ status: DomainStatus }> = ({ status }) => {
if (status === 'validating') {
return <FontAwesomeIcon fixedWidth icon={loadingStatusIcon} spin />;
}
export const DomainRow: FC<DomainRowProps> = ({ domain, editDomainRedirects, defaultRedirects, selectedServer }) => { return status === 'valid'
? <FontAwesomeIcon fixedWidth icon={checkIcon} className="text-muted" />
: <FontAwesomeIcon fixedWidth icon={forbiddenIcon} className="text-danger" />;
};
export const DomainRow: FC<DomainRowProps> = (
{ domain, editDomainRedirects, checkDomainHealth, defaultRedirects, selectedServer },
) => {
const [ isOpen, toggle ] = useToggle(); const [ isOpen, toggle ] = useToggle();
const { domain: authority, isDefault, redirects } = domain; const { domain: authority, isDefault, redirects, status } = domain;
const canEditDomain = !isDefault || supportsDefaultDomainRedirectsEdition(selectedServer); const canEditDomain = !isDefault || supportsDefaultDomainRedirectsEdition(selectedServer);
useEffect(() => {
checkDomainHealth(domain.domain);
}, []);
return ( return (
<tr className="responsive-table__row"> <tr className="responsive-table__row">
<td className="responsive-table__cell" data-th="Is default domain">{isDefault ? <DefaultDomain /> : ''}</td> <td className="responsive-table__cell" data-th="Is default domain">{isDefault ? <DefaultDomain /> : ''}</td>
@@ -51,6 +70,9 @@ export const DomainRow: FC<DomainRowProps> = ({ domain, editDomainRedirects, def
<td className="responsive-table__cell" data-th="Invalid short URL redirect"> <td className="responsive-table__cell" data-th="Invalid short URL redirect">
{redirects?.invalidShortUrlRedirect ?? <Nr fallback={defaultRedirects?.invalidShortUrlRedirect} />} {redirects?.invalidShortUrlRedirect ?? <Nr fallback={defaultRedirects?.invalidShortUrlRedirect} />}
</td> </td>
<td className="responsive-table__cell text-lg-center" data-th="Status">
<StatusIcon status={status} />
</td>
<td className="responsive-table__cell text-right"> <td className="responsive-table__cell text-right">
<span id={!canEditDomain ? 'defaultDomainBtn' : undefined}> <span id={!canEditDomain ? 'defaultDomainBtn' : undefined}>
<Button outline size="sm" disabled={!canEditDomain} onClick={!canEditDomain ? undefined : toggle}> <Button outline size="sm" disabled={!canEditDomain} onClick={!canEditDomain ? undefined : toggle}>

View File

@@ -13,14 +13,15 @@ interface ManageDomainsProps {
listDomains: Function; listDomains: Function;
filterDomains: (searchTerm: string) => void; filterDomains: (searchTerm: string) => void;
editDomainRedirects: (domain: string, redirects: Partial<ShlinkDomainRedirects>) => Promise<void>; editDomainRedirects: (domain: string, redirects: Partial<ShlinkDomainRedirects>) => Promise<void>;
checkDomainHealth: (domain: string) => void;
domainsList: DomainsList; domainsList: DomainsList;
selectedServer: SelectedServer; selectedServer: SelectedServer;
} }
const headers = [ '', 'Domain', 'Base path redirect', 'Regular 404 redirect', 'Invalid short URL redirect', '' ]; const headers = [ '', 'Domain', 'Base path redirect', 'Regular 404 redirect', 'Invalid short URL redirect', '', '' ];
export const ManageDomains: FC<ManageDomainsProps> = ( export const ManageDomains: FC<ManageDomainsProps> = (
{ listDomains, domainsList, filterDomains, editDomainRedirects, selectedServer }, { listDomains, domainsList, filterDomains, editDomainRedirects, checkDomainHealth, selectedServer },
) => { ) => {
const { filteredDomains: domains, defaultRedirects, loading, error, errorData } = domainsList; const { filteredDomains: domains, defaultRedirects, loading, error, errorData } = domainsList;
const resolvedDefaultRedirects = defaultRedirects ?? domains.find(({ isDefault }) => isDefault)?.redirects; const resolvedDefaultRedirects = defaultRedirects ?? domains.find(({ isDefault }) => isDefault)?.redirects;
@@ -55,6 +56,7 @@ export const ManageDomains: FC<ManageDomainsProps> = (
key={domain.domain} key={domain.domain}
domain={domain} domain={domain}
editDomainRedirects={editDomainRedirects} editDomainRedirects={editDomainRedirects}
checkDomainHealth={checkDomainHealth}
defaultRedirects={resolvedDefaultRedirects} defaultRedirects={resolvedDefaultRedirects}
selectedServer={selectedServer} selectedServer={selectedServer}
/> />

View File

@@ -1,6 +1,6 @@
import Bottle from 'bottlejs'; import Bottle from 'bottlejs';
import { ConnectDecorator } from '../../container/types'; import { ConnectDecorator } from '../../container/types';
import { filterDomains, listDomains } from '../reducers/domainsList'; import { checkDomainHealth, filterDomains, listDomains } from '../reducers/domainsList';
import { DomainSelector } from '../DomainSelector'; import { DomainSelector } from '../DomainSelector';
import { ManageDomains } from '../ManageDomains'; import { ManageDomains } from '../ManageDomains';
import { editDomainRedirects } from '../reducers/domainRedirects'; import { editDomainRedirects } from '../reducers/domainRedirects';
@@ -13,13 +13,14 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('ManageDomains', () => ManageDomains); bottle.serviceFactory('ManageDomains', () => ManageDomains);
bottle.decorator('ManageDomains', connect( bottle.decorator('ManageDomains', connect(
[ 'domainsList', 'selectedServer' ], [ 'domainsList', 'selectedServer' ],
[ 'listDomains', 'filterDomains', 'editDomainRedirects' ], [ 'listDomains', 'filterDomains', 'editDomainRedirects', 'checkDomainHealth' ],
)); ));
// Actions // Actions
bottle.serviceFactory('listDomains', listDomains, 'buildShlinkApiClient'); bottle.serviceFactory('listDomains', listDomains, 'buildShlinkApiClient');
bottle.serviceFactory('filterDomains', () => filterDomains); bottle.serviceFactory('filterDomains', () => filterDomains);
bottle.serviceFactory('editDomainRedirects', editDomainRedirects, 'buildShlinkApiClient'); bottle.serviceFactory('editDomainRedirects', editDomainRedirects, 'buildShlinkApiClient');
bottle.serviceFactory('checkDomainHealth', checkDomainHealth, 'buildShlinkApiClient');
}; };
export default provideServices; export default provideServices;

View File

@@ -3,14 +3,22 @@ import { Mock } from 'ts-mockery';
import { Button, UncontrolledTooltip } from 'reactstrap'; import { Button, UncontrolledTooltip } from 'reactstrap';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faBan as forbiddenIcon, faEdit as editIcon } from '@fortawesome/free-solid-svg-icons'; import { faBan as forbiddenIcon, faEdit as editIcon } from '@fortawesome/free-solid-svg-icons';
import { ShlinkDomain, ShlinkDomainRedirects } from '../../src/api/types'; import { ShlinkDomainRedirects } from '../../src/api/types';
import { DomainRow } from '../../src/domains/DomainRow'; import { DomainRow } from '../../src/domains/DomainRow';
import { ReachableServer, SelectedServer } from '../../src/servers/data'; import { ReachableServer, SelectedServer } from '../../src/servers/data';
import { Domain } from '../../src/domains/data';
describe('<DomainRow />', () => { describe('<DomainRow />', () => {
let wrapper: ShallowWrapper; let wrapper: ShallowWrapper;
const createWrapper = (domain: ShlinkDomain, selectedServer = Mock.all<SelectedServer>()) => { const createWrapper = (domain: Domain, selectedServer = Mock.all<SelectedServer>()) => {
wrapper = shallow(<DomainRow domain={domain} editDomainRedirects={jest.fn()} selectedServer={selectedServer} />); wrapper = shallow(
<DomainRow
domain={domain}
selectedServer={selectedServer}
editDomainRedirects={jest.fn()}
checkDomainHealth={jest.fn()}
/>,
);
return wrapper; return wrapper;
}; };
@@ -18,34 +26,34 @@ describe('<DomainRow />', () => {
afterEach(() => wrapper?.unmount()); afterEach(() => wrapper?.unmount());
it.each([ it.each([
[ Mock.of<ShlinkDomain>({ domain: '', isDefault: true }), undefined, 1, 1, 'defaultDomainBtn' ], [ Mock.of<Domain>({ domain: '', isDefault: true }), undefined, 1, 1, 'defaultDomainBtn' ],
[ Mock.of<ShlinkDomain>({ domain: '', isDefault: false }), undefined, 0, 0, undefined ], [ Mock.of<Domain>({ domain: '', isDefault: false }), undefined, 0, 0, undefined ],
[ Mock.of<ShlinkDomain>({ domain: 'foo.com', isDefault: true }), undefined, 1, 1, 'defaultDomainBtn' ], [ Mock.of<Domain>({ domain: 'foo.com', isDefault: true }), undefined, 1, 1, 'defaultDomainBtn' ],
[ Mock.of<ShlinkDomain>({ domain: 'foo.bar.com', isDefault: true }), undefined, 1, 1, 'defaultDomainBtn' ], [ Mock.of<Domain>({ domain: 'foo.bar.com', isDefault: true }), undefined, 1, 1, 'defaultDomainBtn' ],
[ Mock.of<ShlinkDomain>({ domain: 'foo.baz', isDefault: false }), undefined, 0, 0, undefined ], [ Mock.of<Domain>({ domain: 'foo.baz', isDefault: false }), undefined, 0, 0, undefined ],
[ [
Mock.of<ShlinkDomain>({ domain: 'foo.baz', isDefault: true }), Mock.of<Domain>({ domain: 'foo.baz', isDefault: true }),
Mock.of<ReachableServer>({ version: '2.10.0' }), Mock.of<ReachableServer>({ version: '2.10.0' }),
1, 1,
0, 0,
undefined, undefined,
], ],
[ [
Mock.of<ShlinkDomain>({ domain: 'foo.baz', isDefault: true }), Mock.of<Domain>({ domain: 'foo.baz', isDefault: true }),
Mock.of<ReachableServer>({ version: '2.9.0' }), Mock.of<ReachableServer>({ version: '2.9.0' }),
1, 1,
1, 1,
'defaultDomainBtn', 'defaultDomainBtn',
], ],
[ [
Mock.of<ShlinkDomain>({ domain: 'foo.baz', isDefault: false }), Mock.of<Domain>({ domain: 'foo.baz', isDefault: false }),
Mock.of<ReachableServer>({ version: '2.9.0' }), Mock.of<ReachableServer>({ version: '2.9.0' }),
0, 0,
0, 0,
undefined, undefined,
], ],
[ [
Mock.of<ShlinkDomain>({ domain: 'foo.baz', isDefault: false }), Mock.of<Domain>({ domain: 'foo.baz', isDefault: false }),
Mock.of<ReachableServer>({ version: '2.10.0' }), Mock.of<ReachableServer>({ version: '2.10.0' }),
0, 0,
0, 0,
@@ -89,7 +97,7 @@ describe('<DomainRow />', () => {
0, 0,
], ],
])('shows expected redirects', (redirects, expectedNoRedirects) => { ])('shows expected redirects', (redirects, expectedNoRedirects) => {
const wrapper = createWrapper(Mock.of<ShlinkDomain>({ domain: '', isDefault: true, redirects })); const wrapper = createWrapper(Mock.of<Domain>({ domain: '', isDefault: true, redirects }));
const noRedirects = wrapper.find('Nr'); const noRedirects = wrapper.find('Nr');
const cells = wrapper.find('td'); const cells = wrapper.find('td');

View File

@@ -13,14 +13,14 @@ import { SelectedServer } from '../../src/servers/data';
describe('<ManageDomains />', () => { describe('<ManageDomains />', () => {
const listDomains = jest.fn(); const listDomains = jest.fn();
const filterDomains = jest.fn(); const filterDomains = jest.fn();
const editDomainRedirects = jest.fn();
let wrapper: ShallowWrapper; let wrapper: ShallowWrapper;
const createWrapper = (domainsList: DomainsList) => { const createWrapper = (domainsList: DomainsList) => {
wrapper = shallow( wrapper = shallow(
<ManageDomains <ManageDomains
listDomains={listDomains} listDomains={listDomains}
filterDomains={filterDomains} filterDomains={filterDomains}
editDomainRedirects={editDomainRedirects} editDomainRedirects={jest.fn()}
checkDomainHealth={jest.fn()}
domainsList={domainsList} domainsList={domainsList}
selectedServer={Mock.all<SelectedServer>()} selectedServer={Mock.all<SelectedServer>()}
/>, />,
@@ -77,7 +77,7 @@ describe('<ManageDomains />', () => {
const wrapper = createWrapper(Mock.of<DomainsList>({ loading: false, error: false, filteredDomains: [] })); const wrapper = createWrapper(Mock.of<DomainsList>({ loading: false, error: false, filteredDomains: [] }));
const headerCells = wrapper.find('th'); const headerCells = wrapper.find('th');
expect(headerCells).toHaveLength(6); expect(headerCells).toHaveLength(7);
}); });
it('one row when list of domains is empty', () => { it('one row when list of domains is empty', () => {