Dropped support for Shlink 1

This commit is contained in:
Alejandro Celaya 2020-12-12 13:33:21 +01:00
parent b9905c8bf4
commit df6f1b984f
14 changed files with 45 additions and 145 deletions

View file

@ -20,7 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
* *Nothing* * *Nothing*
### Removed ### Removed
* *Nothing* * [#344](https://github.com/shlinkio/shlink-web-client/issues/344) Dropped support for Shlink v1.
### Fixed ### Fixed
* *Nothing* * *Nothing*

View file

@ -115,7 +115,6 @@ const CreateShortUrl = (
); );
const currentServerVersion = isReachableServer(selectedServer) ? selectedServer.version : ''; const currentServerVersion = isReachableServer(selectedServer) ? selectedServer.version : '';
const disableDomain = !versionMatch(currentServerVersion, { minVersion: '1.19.0-beta.1' });
const showDomainSelector = versionMatch(currentServerVersion, { minVersion: '2.4.0' }); const showDomainSelector = versionMatch(currentServerVersion, { minVersion: '2.4.0' });
const disableShortCodeLength = !versionMatch(currentServerVersion, { minVersion: '2.1.0' }); const disableShortCodeLength = !versionMatch(currentServerVersion, { minVersion: '2.1.0' });
@ -141,10 +140,7 @@ const CreateShortUrl = (
title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length', title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length',
}, },
})} })}
{!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text', { {!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text')}
disabled: disableDomain,
...disableDomain && { title: 'Shlink 1.19.0 or higher is required to be able to provide the domain' },
})}
{showDomainSelector && ( {showDomainSelector && (
<FormGroup> <FormGroup>
<DomainSelector <DomainSelector
@ -165,7 +161,6 @@ const CreateShortUrl = (
</div> </div>
</div> </div>
<ForServerVersion minVersion="1.16.0">
<SimpleCard title="Extra validations" className="mb-3"> <SimpleCard title="Extra validations" className="mb-3">
<p> <p>
Make sure the long URL is valid, or ensure an existing short URL is returned if it matches all Make sure the long URL is valid, or ensure an existing short URL is returned if it matches all
@ -194,7 +189,6 @@ const CreateShortUrl = (
<UseExistingIfFoundInfoIcon /> <UseExistingIfFoundInfoIcon />
</p> </p>
</SimpleCard> </SimpleCard>
</ForServerVersion>
</> </>
)} )}

View file

@ -1,6 +1,5 @@
import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons'; import { faTags as tagsIcon } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { FC } from 'react';
import { isEmpty, pipe } from 'ramda'; import { isEmpty, pipe } from 'ramda';
import moment from 'moment'; import moment from 'moment';
import SearchField from '../utils/SearchField'; import SearchField from '../utils/SearchField';
@ -8,7 +7,6 @@ import Tag from '../tags/helpers/Tag';
import DateRangeRow from '../utils/DateRangeRow'; import DateRangeRow from '../utils/DateRangeRow';
import { formatDate } from '../utils/helpers/date'; import { formatDate } from '../utils/helpers/date';
import ColorGenerator from '../utils/services/ColorGenerator'; import ColorGenerator from '../utils/services/ColorGenerator';
import { Versions } from '../utils/helpers/version';
import { ShortUrlsListParams } from './reducers/shortUrlsListParams'; import { ShortUrlsListParams } from './reducers/shortUrlsListParams';
import './SearchBar.scss'; import './SearchBar.scss';
@ -19,9 +17,7 @@ interface SearchBarProps {
const dateOrNull = (date?: string) => date ? moment(date) : null; const dateOrNull = (date?: string) => date ? moment(date) : null;
const SearchBar = (colorGenerator: ColorGenerator, ForServerVersion: FC<Versions>) => ( const SearchBar = (colorGenerator: ColorGenerator) => ({ listShortUrls, shortUrlsListParams }: SearchBarProps) => {
{ listShortUrls, shortUrlsListParams }: SearchBarProps,
) => {
const selectedTags = shortUrlsListParams.tags ?? []; const selectedTags = shortUrlsListParams.tags ?? [];
const setDate = (dateName: 'startDate' | 'endDate') => pipe( const setDate = (dateName: 'startDate' | 'endDate') => pipe(
formatDate(), formatDate(),
@ -36,7 +32,6 @@ const SearchBar = (colorGenerator: ColorGenerator, ForServerVersion: FC<Versions
} }
/> />
<ForServerVersion minVersion="1.21.0">
<div className="mt-3"> <div className="mt-3">
<div className="row"> <div className="row">
<div className="col-lg-8 offset-lg-4 col-xl-6 offset-xl-6"> <div className="col-lg-8 offset-lg-4 col-xl-6 offset-xl-6">
@ -49,7 +44,6 @@ const SearchBar = (colorGenerator: ColorGenerator, ForServerVersion: FC<Versions
</div> </div>
</div> </div>
</div> </div>
</ForServerVersion>
{!isEmpty(selectedTags) && ( {!isEmpty(selectedTags) && (
<h4 className="search-bar__selected-tag mt-3"> <h4 className="search-bar__selected-tag mt-3">

View file

@ -21,7 +21,7 @@ const DeleteShortUrlModal = (
useEffect(() => resetDeleteShortUrl, []); useEffect(() => resetDeleteShortUrl, []);
const { error, errorData } = shortUrlDeletion; const { error, errorData } = shortUrlDeletion;
const errorCode = error && (errorData?.type || errorData?.error); const errorCode = error && errorData?.type;
const hasThresholdError = errorCode === THRESHOLD_REACHED; const hasThresholdError = errorCode === THRESHOLD_REACHED;
const hasErrorOtherThanThreshold = error && errorCode !== THRESHOLD_REACHED; const hasErrorOtherThanThreshold = error && errorCode !== THRESHOLD_REACHED;
const close = pipe(resetDeleteShortUrl, toggle); const close = pipe(resetDeleteShortUrl, toggle);

View file

@ -1,13 +0,0 @@
@import '../../utils/mixins/horizontal-align';
.preview-modal__img {
max-width: 100%;
position: relative;
z-index: 2;
}
.preview-modal__loader {
@include horizontal-align();
z-index: 1;
top: 1rem;
}

View file

@ -1,20 +0,0 @@
import { Modal, ModalBody, ModalHeader } from 'reactstrap';
import { ExternalLink } from 'react-external-link';
import { ShortUrlModalProps } from '../data';
import './PreviewModal.scss';
const PreviewModal = ({ shortUrl: { shortUrl }, toggle, isOpen }: ShortUrlModalProps) => (
<Modal isOpen={isOpen} toggle={toggle} size="lg">
<ModalHeader toggle={toggle}>
Preview for <ExternalLink href={shortUrl}>{shortUrl}</ExternalLink>
</ModalHeader>
<ModalBody>
<div className="text-center">
<p className="preview-modal__loader">Loading...</p>
<img src={`${shortUrl}/preview`} className="preview-modal__img" alt="Preview" />
</div>
</ModalBody>
</Modal>
);
export default PreviewModal;

View file

@ -1,4 +1,3 @@
import { faImage as pictureIcon } from '@fortawesome/free-regular-svg-icons';
import { import {
faTags as tagsIcon, faTags as tagsIcon,
faChartPie as pieChartIcon, faChartPie as pieChartIcon,
@ -15,7 +14,6 @@ import { useToggle } from '../../utils/helpers/hooks';
import { ShortUrl, ShortUrlModalProps } from '../data'; import { ShortUrl, ShortUrlModalProps } from '../data';
import { Versions } from '../../utils/helpers/version'; import { Versions } from '../../utils/helpers/version';
import { SelectedServer } from '../../servers/data'; import { SelectedServer } from '../../servers/data';
import PreviewModal from './PreviewModal';
import QrCodeModal from './QrCodeModal'; import QrCodeModal from './QrCodeModal';
import VisitStatsLink from './VisitStatsLink'; import VisitStatsLink from './VisitStatsLink';
import './ShortUrlsRowMenu.scss'; import './ShortUrlsRowMenu.scss';
@ -35,7 +33,6 @@ const ShortUrlsRowMenu = (
) => ({ shortUrl, selectedServer }: ShortUrlsRowMenuProps) => { ) => ({ shortUrl, selectedServer }: ShortUrlsRowMenuProps) => {
const [ isOpen, toggle ] = useToggle(); const [ isOpen, toggle ] = useToggle();
const [ isQrModalOpen, toggleQrCode ] = useToggle(); const [ isQrModalOpen, toggleQrCode ] = useToggle();
const [ isPreviewModalOpen, togglePreview ] = useToggle();
const [ isTagsModalOpen, toggleTags ] = useToggle(); const [ isTagsModalOpen, toggleTags ] = useToggle();
const [ isMetaModalOpen, toggleMeta ] = useToggle(); const [ isMetaModalOpen, toggleMeta ] = useToggle();
const [ isDeleteModalOpen, toggleDelete ] = useToggle(); const [ isDeleteModalOpen, toggleDelete ] = useToggle();
@ -56,12 +53,10 @@ const ShortUrlsRowMenu = (
</DropdownItem> </DropdownItem>
<EditTagsModal shortUrl={shortUrl} isOpen={isTagsModalOpen} toggle={toggleTags} /> <EditTagsModal shortUrl={shortUrl} isOpen={isTagsModalOpen} toggle={toggleTags} />
<ForServerVersion minVersion="1.18.0">
<DropdownItem onClick={toggleMeta}> <DropdownItem onClick={toggleMeta}>
<FontAwesomeIcon icon={editIcon} fixedWidth /> Edit metadata <FontAwesomeIcon icon={editIcon} fixedWidth /> Edit metadata
</DropdownItem> </DropdownItem>
<EditMetaModal shortUrl={shortUrl} isOpen={isMetaModalOpen} toggle={toggleMeta} /> <EditMetaModal shortUrl={shortUrl} isOpen={isMetaModalOpen} toggle={toggleMeta} />
</ForServerVersion>
<ForServerVersion minVersion="2.1.0"> <ForServerVersion minVersion="2.1.0">
<DropdownItem onClick={toggleEdit}> <DropdownItem onClick={toggleEdit}>
@ -75,13 +70,6 @@ const ShortUrlsRowMenu = (
</DropdownItem> </DropdownItem>
<QrCodeModal shortUrl={shortUrl} isOpen={isQrModalOpen} toggle={toggleQrCode} /> <QrCodeModal shortUrl={shortUrl} isOpen={isQrModalOpen} toggle={toggleQrCode} />
<ForServerVersion maxVersion="1.x">
<DropdownItem onClick={togglePreview}>
<FontAwesomeIcon icon={pictureIcon} fixedWidth /> Preview
</DropdownItem>
<PreviewModal shortUrl={shortUrl} isOpen={isPreviewModalOpen} toggle={togglePreview} />
</ForServerVersion>
<DropdownItem divider /> <DropdownItem divider />
<DropdownItem className="short-urls-row-menu__dropdown-item--danger" onClick={toggleDelete}> <DropdownItem className="short-urls-row-menu__dropdown-item--danger" onClick={toggleDelete}>

View file

@ -70,7 +70,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.decorator('EditShortUrlModal', connect([ 'shortUrlEdition' ], [ 'editShortUrl' ])); bottle.decorator('EditShortUrlModal', connect([ 'shortUrlEdition' ], [ 'editShortUrl' ]));
// Services // Services
bottle.serviceFactory('SearchBar', SearchBar, 'ColorGenerator', 'ForServerVersion'); bottle.serviceFactory('SearchBar', SearchBar, 'ColorGenerator');
bottle.decorator('SearchBar', connect([ 'shortUrlsListParams' ], [ 'listShortUrls' ])); bottle.decorator('SearchBar', connect([ 'shortUrlsListParams' ], [ 'listShortUrls' ]));
// Actions // Actions

View file

@ -125,7 +125,7 @@ export default class ShlinkApiClient {
// When the request is not invalid or we have already tried both API versions, throw the error and let the // When the request is not invalid or we have already tried both API versions, throw the error and let the
// caller handle it // caller handle it
if (!apiVersionIsNotSupported || this.apiVersion === 1) { if (!apiVersionIsNotSupported || this.apiVersion === 2) {
throw e; throw e;
} }

View file

@ -65,8 +65,6 @@ export interface ProblemDetailsError {
detail: string; detail: string;
title: string; title: string;
status: number; status: number;
error?: string; // Deprecated
message?: string; // Deprecated
[extraProps: string]: any; [extraProps: string]: any;
} }

View file

@ -9,7 +9,7 @@ import ColorGenerator from '../../src/utils/services/ColorGenerator';
describe('<SearchBar />', () => { describe('<SearchBar />', () => {
let wrapper: ShallowWrapper; let wrapper: ShallowWrapper;
const listShortUrlsMock = jest.fn(); const listShortUrlsMock = jest.fn();
const SearchBar = searchBarCreator(Mock.all<ColorGenerator>(), () => null); const SearchBar = searchBarCreator(Mock.all<ColorGenerator>());
afterEach(jest.clearAllMocks); afterEach(jest.clearAllMocks);
afterEach(() => wrapper?.unmount()); afterEach(() => wrapper?.unmount());

View file

@ -33,18 +33,10 @@ describe('<DeleteShortUrlModal />', () => {
afterEach(jest.clearAllMocks); afterEach(jest.clearAllMocks);
it.each([ it.each([
[
{ error: 'INVALID_SHORTCODE_DELETION' },
'This short URL has received too many visits, and therefore, it cannot be deleted.',
],
[ [
{ type: 'INVALID_SHORTCODE_DELETION' }, { type: 'INVALID_SHORTCODE_DELETION' },
'This short URL has received too many visits, and therefore, it cannot be deleted.', 'This short URL has received too many visits, and therefore, it cannot be deleted.',
], ],
[
{ error: 'INVALID_SHORTCODE_DELETION', threshold: 35 },
'This short URL has received more than 35 visits, and therefore, it cannot be deleted.',
],
[ [
{ type: 'INVALID_SHORTCODE_DELETION', threshold: 8 }, { type: 'INVALID_SHORTCODE_DELETION', threshold: 8 },
'This short URL has received more than 8 visits, and therefore, it cannot be deleted.', 'This short URL has received more than 8 visits, and therefore, it cannot be deleted.',
@ -67,7 +59,7 @@ describe('<DeleteShortUrlModal />', () => {
loading: false, loading: false,
error: true, error: true,
shortCode: 'abc123', shortCode: 'abc123',
errorData: Mock.of<ProblemDetailsError>({ error: 'OTHER_ERROR' }), errorData: Mock.of<ProblemDetailsError>({ type: 'OTHER_ERROR' }),
}); });
const error = wrapper.find('.bg-danger'); const error = wrapper.find('.bg-danger');

View file

@ -1,29 +0,0 @@
import { shallow, ShallowWrapper } from 'enzyme';
import { ExternalLink } from 'react-external-link';
import { Mock } from 'ts-mockery';
import PreviewModal from '../../../src/short-urls/helpers/PreviewModal';
import { ShortUrl } from '../../../src/short-urls/data';
describe('<PreviewModal />', () => {
let wrapper: ShallowWrapper;
const shortUrl = 'https://doma.in/abc123';
beforeEach(() => {
wrapper = shallow(<PreviewModal shortUrl={Mock.of<ShortUrl>({ shortUrl })} isOpen={true} toggle={() => {}} />);
});
afterEach(() => wrapper.unmount());
it('shows an external link to the URL', () => {
const externalLink = wrapper.find(ExternalLink);
expect(externalLink).toHaveLength(1);
expect(externalLink.prop('href')).toEqual(shortUrl);
});
it('displays an image with the preview of the URL', () => {
const img = wrapper.find('img');
expect(img).toHaveLength(1);
expect(img.prop('src')).toEqual(`${shortUrl}/preview`);
});
});

View file

@ -2,7 +2,6 @@ import { shallow, ShallowWrapper } from 'enzyme';
import { ButtonDropdown, DropdownItem } from 'reactstrap'; import { ButtonDropdown, DropdownItem } from 'reactstrap';
import { Mock } from 'ts-mockery'; import { Mock } from 'ts-mockery';
import createShortUrlsRowMenu from '../../../src/short-urls/helpers/ShortUrlsRowMenu'; import createShortUrlsRowMenu from '../../../src/short-urls/helpers/ShortUrlsRowMenu';
import PreviewModal from '../../../src/short-urls/helpers/PreviewModal';
import QrCodeModal from '../../../src/short-urls/helpers/QrCodeModal'; import QrCodeModal from '../../../src/short-urls/helpers/QrCodeModal';
import { ReachableServer } from '../../../src/servers/data'; import { ReachableServer } from '../../../src/servers/data';
import { ShortUrl } from '../../../src/short-urls/data'; import { ShortUrl } from '../../../src/short-urls/data';
@ -38,13 +37,11 @@ describe('<ShortUrlsRowMenu />', () => {
const wrapper = createWrapper(); const wrapper = createWrapper();
const deleteShortUrlModal = wrapper.find(DeleteShortUrlModal); const deleteShortUrlModal = wrapper.find(DeleteShortUrlModal);
const editTagsModal = wrapper.find(EditTagsModal); const editTagsModal = wrapper.find(EditTagsModal);
const previewModal = wrapper.find(PreviewModal);
const qrCodeModal = wrapper.find(QrCodeModal); const qrCodeModal = wrapper.find(QrCodeModal);
const editModal = wrapper.find(EditShortUrlModal); const editModal = wrapper.find(EditShortUrlModal);
expect(deleteShortUrlModal).toHaveLength(1); expect(deleteShortUrlModal).toHaveLength(1);
expect(editTagsModal).toHaveLength(1); expect(editTagsModal).toHaveLength(1);
expect(previewModal).toHaveLength(1);
expect(qrCodeModal).toHaveLength(1); expect(qrCodeModal).toHaveLength(1);
expect(editModal).toHaveLength(1); expect(editModal).toHaveLength(1);
}); });
@ -53,7 +50,7 @@ describe('<ShortUrlsRowMenu />', () => {
const wrapper = createWrapper(); const wrapper = createWrapper();
const items = wrapper.find(DropdownItem); const items = wrapper.find(DropdownItem);
expect(items).toHaveLength(8); expect(items).toHaveLength(7);
expect(items.find('[divider]')).toHaveLength(1); expect(items.find('[divider]')).toHaveLength(1);
}); });
@ -68,7 +65,6 @@ describe('<ShortUrlsRowMenu />', () => {
it('DeleteShortUrlModal', () => assert(DeleteShortUrlModal)); it('DeleteShortUrlModal', () => assert(DeleteShortUrlModal));
it('EditTagsModal', () => assert(EditTagsModal)); it('EditTagsModal', () => assert(EditTagsModal));
it('PreviewModal', () => assert(PreviewModal));
it('QrCodeModal', () => assert(QrCodeModal)); it('QrCodeModal', () => assert(QrCodeModal));
it('EditShortUrlModal', () => assert(EditShortUrlModal)); it('EditShortUrlModal', () => assert(EditShortUrlModal));
it('EditShortUrlModal', () => assert(ButtonDropdown)); it('EditShortUrlModal', () => assert(ButtonDropdown));