From 5224e7b4ef6bcd316989e3413c2cdcc9e8812eec Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 6 Mar 2021 09:58:29 +0100 Subject: [PATCH] Created new feature checkers --- src/common/MenuLayout.tsx | 6 ++--- src/short-urls/CreateShortUrl.tsx | 10 ++++----- src/short-urls/ShortUrlsTable.tsx | 4 ++-- src/short-urls/helpers/QrCodeModal.tsx | 12 +++++----- src/short-urls/reducers/shortUrlTags.ts | 10 +++------ src/utils/helpers/features.ts | 18 ++++++++++++++- src/utils/helpers/version.ts | 8 +++---- test/utils/helpers/version.test.ts | 30 +++++++++++++------------ 8 files changed, 56 insertions(+), 42 deletions(-) diff --git a/src/common/MenuLayout.tsx b/src/common/MenuLayout.tsx index 57bd1189..3743424a 100644 --- a/src/common/MenuLayout.tsx +++ b/src/common/MenuLayout.tsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import classNames from 'classnames'; import { withSelectedServer } from '../servers/helpers/withSelectedServer'; import { useSwipeable, useToggle } from '../utils/helpers/hooks'; -import { versionMatch } from '../utils/helpers/version'; +import { supportsOrphanVisits, supportsTagVisits } from '../utils/helpers/features'; import { isReachableServer } from '../servers/data'; import NotFound from './NotFound'; import { AsideMenuProps } from './AsideMenu'; @@ -30,8 +30,8 @@ const MenuLayout = ( return ; } - const addTagsVisitsRoute = versionMatch(selectedServer.version, { minVersion: '2.2.0' }); - const addOrphanVisitsRoute = versionMatch(selectedServer.version, { minVersion: '2.6.0' }); + const addTagsVisitsRoute = supportsTagVisits(selectedServer); + const addOrphanVisitsRoute = supportsOrphanVisits(selectedServer); const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible }); const swipeableProps = useSwipeable(showSidebar, hideSidebar); diff --git a/src/short-urls/CreateShortUrl.tsx b/src/short-urls/CreateShortUrl.tsx index 7bf2fe1f..bf48e394 100644 --- a/src/short-urls/CreateShortUrl.tsx +++ b/src/short-urls/CreateShortUrl.tsx @@ -5,9 +5,10 @@ import { InputType } from 'reactstrap/lib/Input'; import * as m from 'moment'; import DateInput, { DateInputProps } from '../utils/DateInput'; import Checkbox from '../utils/Checkbox'; -import { versionMatch, Versions } from '../utils/helpers/version'; +import { Versions } from '../utils/helpers/version'; +import { supportsListingDomains, supportsSettingShortCodeLength } from '../utils/helpers/features'; import { handleEventPreventingDefault, hasValue } from '../utils/utils'; -import { isReachableServer, SelectedServer } from '../servers/data'; +import { SelectedServer } from '../servers/data'; import { formatIsoDate } from '../utils/helpers/date'; import { TagsSelectorProps } from '../tags/helpers/TagsSelector'; import { DomainSelectorProps } from '../domains/DomainSelector'; @@ -117,9 +118,8 @@ const CreateShortUrl = ( ); - const currentServerVersion = isReachableServer(selectedServer) ? selectedServer.version : ''; - const showDomainSelector = versionMatch(currentServerVersion, { minVersion: '2.4.0' }); - const disableShortCodeLength = !versionMatch(currentServerVersion, { minVersion: '2.1.0' }); + const showDomainSelector = supportsListingDomains(selectedServer); + const disableShortCodeLength = !supportsSettingShortCodeLength(selectedServer); return (
diff --git a/src/short-urls/ShortUrlsTable.tsx b/src/short-urls/ShortUrlsTable.tsx index 9bc0c9d6..f1f6c05c 100644 --- a/src/short-urls/ShortUrlsTable.tsx +++ b/src/short-urls/ShortUrlsTable.tsx @@ -2,7 +2,7 @@ import { FC, ReactNode } from 'react'; import { isEmpty } from 'ramda'; import classNames from 'classnames'; import { SelectedServer } from '../servers/data'; -import { titleIsSupported } from '../utils/helpers/features'; +import { supportsShortUrlTitle } from '../utils/helpers/features'; import { ShortUrlsList as ShortUrlsListState } from './reducers/shortUrlsList'; import { ShortUrlsRowProps } from './helpers/ShortUrlsRow'; import { OrderableFields } from './reducers/shortUrlsListParams'; @@ -29,7 +29,7 @@ export const ShortUrlsTable = (ShortUrlsRow: FC) => ({ const actionableFieldClasses = classNames({ 'short-urls-table__header-cell--with-action': !!orderByColumn }); const orderableColumnsClasses = classNames('short-urls-table__header-cell', actionableFieldClasses); const tableClasses = classNames('table table-hover', className); - const supportsTitle = titleIsSupported(selectedServer); + const supportsTitle = supportsShortUrlTitle(selectedServer); const renderShortUrls = () => { if (error) { diff --git a/src/short-urls/helpers/QrCodeModal.tsx b/src/short-urls/helpers/QrCodeModal.tsx index 4bde939e..b4bd0abb 100644 --- a/src/short-urls/helpers/QrCodeModal.tsx +++ b/src/short-urls/helpers/QrCodeModal.tsx @@ -3,15 +3,15 @@ import { Modal, DropdownItem, FormGroup, ModalBody, ModalHeader, Row } from 'rea import { ExternalLink } from 'react-external-link'; import classNames from 'classnames'; import { ShortUrlModalProps } from '../data'; -import { ReachableServer } from '../../servers/data'; -import { versionMatch } from '../../utils/helpers/version'; +import { SelectedServer } from '../../servers/data'; import { DropdownBtn } from '../../utils/DropdownBtn'; import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon'; import { buildQrCodeUrl, QrCodeCapabilities, QrCodeFormat } from '../../utils/helpers/qrCodes'; +import { supportsQrCodeSizeInQuery, supportsQrCodeSvgFormat, supportsQrCodeMargin } from '../../utils/helpers/features'; import './QrCodeModal.scss'; interface QrCodeModalConnectProps extends ShortUrlModalProps { - selectedServer: ReachableServer; + selectedServer: SelectedServer; } const QrCodeModal = ({ shortUrl: { shortUrl }, toggle, isOpen, selectedServer }: QrCodeModalConnectProps) => { @@ -19,9 +19,9 @@ const QrCodeModal = ({ shortUrl: { shortUrl }, toggle, isOpen, selectedServer }: const [ margin, setMargin ] = useState(0); const [ format, setFormat ] = useState('png'); const capabilities: QrCodeCapabilities = useMemo(() => ({ - useSizeInPath: !versionMatch(selectedServer.version, { minVersion: '2.5.0' }), - svgIsSupported: versionMatch(selectedServer.version, { minVersion: '2.4.0' }), - marginIsSupported: versionMatch(selectedServer.version, { minVersion: '2.6.0' }), + useSizeInPath: !supportsQrCodeSizeInQuery(selectedServer), + svgIsSupported: supportsQrCodeSvgFormat(selectedServer), + marginIsSupported: supportsQrCodeMargin(selectedServer), }), [ selectedServer ]); const qrCodeUrl = useMemo( () => buildQrCodeUrl(shortUrl, { size, format, margin }, capabilities), diff --git a/src/short-urls/reducers/shortUrlTags.ts b/src/short-urls/reducers/shortUrlTags.ts index 0efc999f..cf0bfd8c 100644 --- a/src/short-urls/reducers/shortUrlTags.ts +++ b/src/short-urls/reducers/shortUrlTags.ts @@ -7,8 +7,7 @@ import { ShortUrlIdentifier } from '../data'; import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder'; import { ProblemDetailsError } from '../../api/types'; import { parseApiError } from '../../api/utils'; -import { isReachableServer } from '../../servers/data'; -import { versionMatch } from '../../utils/helpers/version'; +import { supportsTagsInPatch } from '../../utils/helpers/features'; /* eslint-disable padding-line-between-statements */ export const EDIT_SHORT_URL_TAGS_START = 'shlink/shortUrlTags/EDIT_SHORT_URL_TAGS_START'; @@ -54,15 +53,12 @@ export const editShortUrlTags = (buildShlinkApiClient: ShlinkApiClientBuilder) = ) => async (dispatch: Dispatch, getState: GetState) => { dispatch({ type: EDIT_SHORT_URL_TAGS_START }); const { selectedServer } = getState(); - const supportsTagsInPatch = isReachableServer(selectedServer) && versionMatch( - selectedServer.version, - { minVersion: '2.6.0' }, - ); + const tagsInPatch = supportsTagsInPatch(selectedServer); const { updateShortUrlTags, updateShortUrlMeta } = buildShlinkApiClient(getState); try { const normalizedTags = await ( - supportsTagsInPatch + tagsInPatch ? updateShortUrlMeta(shortCode, domain, { tags }).then(prop('tags')) : updateShortUrlTags(shortCode, domain, tags) ); diff --git a/src/utils/helpers/features.ts b/src/utils/helpers/features.ts index 7a382477..44279433 100644 --- a/src/utils/helpers/features.ts +++ b/src/utils/helpers/features.ts @@ -4,4 +4,20 @@ import { versionMatch, Versions } from './version'; const serverMatchesVersions = (versions: Versions) => (selectedServer: SelectedServer): boolean => isReachableServer(selectedServer) && versionMatch(selectedServer.version, versions); -export const titleIsSupported = serverMatchesVersions({ minVersion: '2.6.0' }); +export const supportsSettingShortCodeLength = serverMatchesVersions({ minVersion: '2.1.0' }); + +export const supportsTagVisits = serverMatchesVersions({ minVersion: '2.2.0' }); + +export const supportsListingDomains = serverMatchesVersions({ minVersion: '2.4.0' }); + +export const supportsQrCodeSvgFormat = supportsListingDomains; + +export const supportsQrCodeSizeInQuery = serverMatchesVersions({ minVersion: '2.5.0' }); + +export const supportsShortUrlTitle = serverMatchesVersions({ minVersion: '2.6.0' }); + +export const supportsOrphanVisits = supportsShortUrlTitle; + +export const supportsQrCodeMargin = supportsShortUrlTitle; + +export const supportsTagsInPatch = supportsShortUrlTitle; diff --git a/src/utils/helpers/version.ts b/src/utils/helpers/version.ts index e3695eb1..41cf9ade 100644 --- a/src/utils/helpers/version.ts +++ b/src/utils/helpers/version.ts @@ -13,7 +13,7 @@ export interface Versions { minVersion?: SemVerPattern; } -export type SemVer = `${bigint}.${bigint}.${bigint}`; +export type SemVer = `${bigint}.${bigint}.${bigint}` | 'latest'; export const versionMatch = (versionToMatch: SemVer | Empty, { maxVersion, minVersion }: Versions): boolean => { if (!hasValue(versionToMatch)) { @@ -26,7 +26,7 @@ export const versionMatch = (versionToMatch: SemVer | Empty, { maxVersion, minVe return matchesMaxVersion && matchesMinVersion; }; -const versionIsValidSemVer = memoizeWith(identity, (version: string): version is SemVerPattern => { +const versionIsValidSemVer = memoizeWith(identity, (version: string): version is SemVer => { try { return compare(version, version, '='); } catch (e) { @@ -36,5 +36,5 @@ const versionIsValidSemVer = memoizeWith(identity, (version: string): version is export const versionToPrintable = (version: string) => !versionIsValidSemVer(version) ? version : `v${version}`; -export const versionToSemVer = (defaultValue = 'latest') => - (version: string) => versionIsValidSemVer(version) ? version : defaultValue; +export const versionToSemVer = (defaultValue: SemVer = 'latest') => + (version: string): SemVer => versionIsValidSemVer(version) ? version : defaultValue; diff --git a/test/utils/helpers/version.test.ts b/test/utils/helpers/version.test.ts index adcfdee9..8e817c2f 100644 --- a/test/utils/helpers/version.test.ts +++ b/test/utils/helpers/version.test.ts @@ -1,21 +1,23 @@ -import { versionMatch } from '../../../src/utils/helpers/version'; +import { Mock } from 'ts-mockery'; +import { SemVer, versionMatch, Versions } from '../../../src/utils/helpers/version'; +import { Empty } from '../../../src/utils/utils'; describe('version', () => { describe('versionMatch', () => { it.each([ - [ undefined, {}, false ], - [ null, {}, false ], - [ '', {}, false ], - [[], {}, false ], - [ '2.8.3', {}, true ], - [ '2.8.3', { minVersion: '2.0.0' }, true ], - [ '2.0.0', { minVersion: '2.0.0' }, true ], - [ '1.8.0', { maxVersion: '1.8.0' }, true ], - [ '1.7.1', { maxVersion: '1.8.0' }, true ], - [ '1.7.3', { minVersion: '1.7.0', maxVersion: '1.8.0' }, true ], - [ '1.8.3', { minVersion: '2.0.0' }, false ], - [ '1.8.3', { maxVersion: '1.8.0' }, false ], - [ '1.8.3', { minVersion: '1.7.0', maxVersion: '1.8.0' }, false ], + [ undefined, Mock.all(), false ], + [ null, Mock.all(), false ], + [ '' as Empty, Mock.all(), false ], + [[], Mock.all(), false ], + [ '2.8.3' as SemVer, Mock.all(), true ], + [ '2.8.3' as SemVer, Mock.of({ minVersion: '2.0.0' }), true ], + [ '2.0.0' as SemVer, Mock.of({ minVersion: '2.0.0' }), true ], + [ '1.8.0' as SemVer, Mock.of({ maxVersion: '1.8.0' }), true ], + [ '1.7.1' as SemVer, Mock.of({ maxVersion: '1.8.0' }), true ], + [ '1.7.3' as SemVer, Mock.of({ minVersion: '1.7.0', maxVersion: '1.8.0' }), true ], + [ '1.8.3' as SemVer, Mock.of({ minVersion: '2.0.0' }), false ], + [ '1.8.3' as SemVer, Mock.of({ maxVersion: '1.8.0' }), false ], + [ '1.8.3' as SemVer, Mock.of({ minVersion: '1.7.0', maxVersion: '1.8.0' }), false ], ])('properly matches versions based on what is provided', (version, versionConstraints, expected) => { expect(versionMatch(version, versionConstraints)).toEqual(expected); });