From 95439e560205a76daa6277845fbade45f123fd3e Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 11 Mar 2023 10:33:03 +0100 Subject: [PATCH] Convert feature flags into hooks --- src/common/MenuLayout.tsx | 6 ++--- src/domains/helpers/DomainDropdown.tsx | 6 ++--- src/servers/Overview.tsx | 4 +-- src/short-urls/ShortUrlForm.tsx | 4 +-- src/short-urls/ShortUrlsFilteringBar.tsx | 6 ++--- src/short-urls/ShortUrlsList.tsx | 5 ++-- src/short-urls/helpers/QrCodeModal.tsx | 4 +-- src/utils/helpers/features.ts | 32 +++++++++++++++++------- 8 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/common/MenuLayout.tsx b/src/common/MenuLayout.tsx index f7c3b137..092db5bb 100644 --- a/src/common/MenuLayout.tsx +++ b/src/common/MenuLayout.tsx @@ -6,7 +6,7 @@ import { useEffect } from 'react'; import { Navigate, Route, Routes, useLocation } from 'react-router-dom'; import { isReachableServer } from '../servers/data'; import { withSelectedServer } from '../servers/helpers/withSelectedServer'; -import { supportsDomainVisits, supportsNonOrphanVisits } from '../utils/helpers/features'; +import { useFeature } from '../utils/helpers/features'; import { useSwipeable, useToggle } from '../utils/helpers/hooks'; import type { AsideMenuProps } from './AsideMenu'; import { NotFound } from './NotFound'; @@ -46,8 +46,8 @@ export const MenuLayout = ( return ; } - const addNonOrphanVisitsRoute = supportsNonOrphanVisits(selectedServer); - const addDomainVisitsRoute = supportsDomainVisits(selectedServer); + const addNonOrphanVisitsRoute = useFeature('nonOrphanVisits', selectedServer); + const addDomainVisitsRoute = useFeature('domainVisits', selectedServer); const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible }); const swipeableProps = useSwipeable(showSidebar, hideSidebar); diff --git a/src/domains/helpers/DomainDropdown.tsx b/src/domains/helpers/DomainDropdown.tsx index 15fda949..6d7ba670 100644 --- a/src/domains/helpers/DomainDropdown.tsx +++ b/src/domains/helpers/DomainDropdown.tsx @@ -6,7 +6,7 @@ import { DropdownItem } from 'reactstrap'; import type { SelectedServer } from '../../servers/data'; import { getServerId } from '../../servers/data'; import { DropdownBtnMenu } from '../../utils/DropdownBtnMenu'; -import { supportsDefaultDomainRedirectsEdition, supportsDomainVisits } from '../../utils/helpers/features'; +import { useFeature } from '../../utils/helpers/features'; import { useToggle } from '../../utils/helpers/hooks'; import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits'; import type { Domain } from '../data'; @@ -23,8 +23,8 @@ export const DomainDropdown: FC = ({ domain, editDomainRedi const [isOpen, toggle] = useToggle(); const [isModalOpen, toggleModal] = useToggle(); const { isDefault } = domain; - const canBeEdited = !isDefault || supportsDefaultDomainRedirectsEdition(selectedServer); - const withVisits = supportsDomainVisits(selectedServer); + const canBeEdited = !isDefault || useFeature('defaultDomainRedirectsEdition', selectedServer); + const withVisits = useFeature('domainVisits', selectedServer); const serverId = getServerId(selectedServer); return ( diff --git a/src/servers/Overview.tsx b/src/servers/Overview.tsx index dfd39808..58f55668 100644 --- a/src/servers/Overview.tsx +++ b/src/servers/Overview.tsx @@ -10,7 +10,7 @@ import type { ShortUrlsList as ShortUrlsListState } from '../short-urls/reducers import { ITEMS_IN_OVERVIEW_PAGE } from '../short-urls/reducers/shortUrlsList'; import type { ShortUrlsTableType } from '../short-urls/ShortUrlsTable'; import type { TagsList } from '../tags/reducers/tagsList'; -import { supportsNonOrphanVisits } from '../utils/helpers/features'; +import { useFeature } from '../utils/helpers/features'; import { prettify } from '../utils/helpers/numbers'; import type { VisitsOverview } from '../visits/reducers/visitsOverview'; import type { SelectedServer } from './data'; @@ -43,7 +43,7 @@ export const Overview = ( const { loading: loadingTags } = tagsList; const { loading: loadingVisits, visitsCount, orphanVisitsCount } = visitsOverview; const serverId = getServerId(selectedServer); - const linkToNonOrphanVisits = supportsNonOrphanVisits(selectedServer); + const linkToNonOrphanVisits = useFeature('nonOrphanVisits', selectedServer); const navigate = useNavigate(); useEffect(() => { diff --git a/src/short-urls/ShortUrlForm.tsx b/src/short-urls/ShortUrlForm.tsx index 1824ec99..eb2e2c23 100644 --- a/src/short-urls/ShortUrlForm.tsx +++ b/src/short-urls/ShortUrlForm.tsx @@ -11,7 +11,7 @@ import { Checkbox } from '../utils/Checkbox'; import type { DateTimeInputProps } from '../utils/dates/DateTimeInput'; import { DateTimeInput } from '../utils/dates/DateTimeInput'; import { formatIsoDate } from '../utils/helpers/date'; -import { supportsForwardQuery } from '../utils/helpers/features'; +import { useFeature } from '../utils/helpers/features'; import { SimpleCard } from '../utils/SimpleCard'; import type { OptionalString } from '../utils/utils'; import { handleEventPreventingDefault, hasValue } from '../utils/utils'; @@ -116,7 +116,7 @@ export const ShortUrlForm = ( ); - const showForwardQueryControl = supportsForwardQuery(selectedServer); + const showForwardQueryControl = useFeature('forwardQuery', selectedServer); return (
diff --git a/src/short-urls/ShortUrlsFilteringBar.tsx b/src/short-urls/ShortUrlsFilteringBar.tsx index 9d01f500..b0394d06 100644 --- a/src/short-urls/ShortUrlsFilteringBar.tsx +++ b/src/short-urls/ShortUrlsFilteringBar.tsx @@ -11,7 +11,7 @@ import { DateRangeSelector } from '../utils/dates/DateRangeSelector'; import { formatIsoDate } from '../utils/helpers/date'; import type { DateRange } from '../utils/helpers/dateIntervals'; import { datesToDateRange } from '../utils/helpers/dateIntervals'; -import { supportsAllTagsFiltering, supportsFilterDisabledUrls } from '../utils/helpers/features'; +import { useFeature } from '../utils/helpers/features'; import type { OrderDir } from '../utils/helpers/ordering'; import { OrderingDropdown } from '../utils/OrderingDropdown'; import { SearchField } from '../utils/SearchField'; @@ -46,7 +46,7 @@ export const ShortUrlsFilteringBar = ( excludePastValidUntil, tagsMode = 'any', } = filter; - const supportsDisabledFiltering = supportsFilterDisabledUrls(selectedServer); + const supportsDisabledFiltering = useFeature('filterDisabledUrls', selectedServer); const setDates = pipe( ({ startDate: theStartDate, endDate: theEndDate }: DateRange) => ({ @@ -60,7 +60,7 @@ export const ShortUrlsFilteringBar = ( (searchTerm) => toFirstPage({ search: searchTerm }), ); const changeTagSelection = (selectedTags: string[]) => toFirstPage({ tags: selectedTags }); - const canChangeTagsMode = supportsAllTagsFiltering(selectedServer); + const canChangeTagsMode = useFeature('allTagsFiltering', selectedServer); const toggleTagsMode = pipe( () => (tagsMode === 'any' ? 'all' : 'any'), (mode) => toFirstPage({ tagsMode: mode }), diff --git a/src/short-urls/ShortUrlsList.tsx b/src/short-urls/ShortUrlsList.tsx index a5afaf7e..1f9e8f15 100644 --- a/src/short-urls/ShortUrlsList.tsx +++ b/src/short-urls/ShortUrlsList.tsx @@ -9,7 +9,7 @@ import type { SelectedServer } from '../servers/data'; import { getServerId } from '../servers/data'; import type { Settings } from '../settings/reducers/settings'; import { DEFAULT_SHORT_URLS_ORDERING } from '../settings/reducers/settings'; -import { supportsExcludeBotsOnShortUrls } from '../utils/helpers/features'; +import { useFeature } from '../utils/helpers/features'; import type { OrderDir } from '../utils/helpers/ordering'; import { determineOrderDir } from '../utils/helpers/ordering'; import { TableOrderIcon } from '../utils/table/TableOrderIcon'; @@ -52,6 +52,7 @@ export const ShortUrlsList = ( ); const { pagination } = shortUrlsList?.shortUrls ?? {}; const doExcludeBots = excludeBots ?? settings.visits?.excludeBots; + const supportsExcludingBots = useFeature('excludeBotsOnShortUrls', selectedServer); const handleOrderBy = (field?: ShortUrlsOrderableFields, dir?: OrderDir) => { toFirstPage({ orderBy: { field, dir } }); setActualOrderBy({ field, dir }); @@ -65,7 +66,7 @@ export const ShortUrlsList = ( (updatedTags) => toFirstPage({ tags: updatedTags }), ); const parseOrderByForShlink = ({ field, dir }: ShortUrlsOrder): ShlinkShortUrlsOrder => { - if (supportsExcludeBotsOnShortUrls(selectedServer) && doExcludeBots && field === 'visits') { + if (supportsExcludingBots && doExcludeBots && field === 'visits') { return { field: 'nonBotVisits', dir }; } diff --git a/src/short-urls/helpers/QrCodeModal.tsx b/src/short-urls/helpers/QrCodeModal.tsx index e0bdb25b..6fe5a562 100644 --- a/src/short-urls/helpers/QrCodeModal.tsx +++ b/src/short-urls/helpers/QrCodeModal.tsx @@ -6,7 +6,7 @@ import { Button, FormGroup, Modal, ModalBody, ModalHeader, Row } from 'reactstra import type { ImageDownloader } from '../../common/services/ImageDownloader'; import type { SelectedServer } from '../../servers/data'; import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon'; -import { supportsNonRestCors } from '../../utils/helpers/features'; +import { useFeature } from '../../utils/helpers/features'; import type { QrCodeFormat, QrErrorCorrection } from '../../utils/helpers/qrCodes'; import { buildQrCodeUrl } from '../../utils/helpers/qrCodes'; import type { ShortUrlModalProps } from '../data'; @@ -25,7 +25,7 @@ export const QrCodeModal = (imageDownloader: ImageDownloader) => ( const [margin, setMargin] = useState(0); const [format, setFormat] = useState('png'); const [errorCorrection, setErrorCorrection] = useState('L'); - const displayDownloadBtn = supportsNonRestCors(selectedServer); + const displayDownloadBtn = useFeature('nonRestCors', selectedServer); const qrCodeUrl = useMemo( () => buildQrCodeUrl(shortUrl, { size, format, margin, errorCorrection }), [shortUrl, size, format, margin, errorCorrection], diff --git a/src/utils/helpers/features.ts b/src/utils/helpers/features.ts index d4700802..388c85e0 100644 --- a/src/utils/helpers/features.ts +++ b/src/utils/helpers/features.ts @@ -1,16 +1,30 @@ +import { useMemo } from 'react'; import type { SelectedServer } from '../../servers/data'; import { isReachableServer } from '../../servers/data'; +import { selectServer } from '../../servers/reducers/selectedServer'; import type { SemVerPattern } from './version'; import { versionMatch } from './version'; -const serverMatchesMinVersion = (minVersion: SemVerPattern) => (selectedServer: SelectedServer): boolean => +const matchesMinVersion = (minVersion: SemVerPattern) => (selectedServer: SelectedServer): boolean => isReachableServer(selectedServer) && versionMatch(selectedServer.version, { minVersion }); -export const supportsForwardQuery = serverMatchesMinVersion('2.9.0'); -export const supportsNonRestCors = supportsForwardQuery; -export const supportsDefaultDomainRedirectsEdition = serverMatchesMinVersion('2.10.0'); -export const supportsNonOrphanVisits = serverMatchesMinVersion('3.0.0'); -export const supportsAllTagsFiltering = supportsNonOrphanVisits; -export const supportsDomainVisits = serverMatchesMinVersion('3.1.0'); -export const supportsExcludeBotsOnShortUrls = serverMatchesMinVersion('3.4.0'); -export const supportsFilterDisabledUrls = supportsExcludeBotsOnShortUrls; +export const supportedFeatures = { + forwardQuery: matchesMinVersion('2.9.0'), + nonRestCors: matchesMinVersion('2.9.0'), + defaultDomainRedirectsEdition: matchesMinVersion('2.10.0'), + nonOrphanVisits: matchesMinVersion('3.0.0'), + allTagsFiltering: matchesMinVersion('3.0.0'), + domainVisits: matchesMinVersion('3.1.0'), + excludeBotsOnShortUrls: matchesMinVersion('3.4.0'), + filterDisabledUrls: matchesMinVersion('3.4.0'), + deviceLongUrls: matchesMinVersion('3.5.0'), +} as const; + +Object.freeze(supportedFeatures); + +type Features = keyof typeof supportedFeatures; + +export const useFeature = (feature: Features, selectedServer: SelectedServer) => useMemo( + () => supportedFeatures[feature](selectedServer), + [feature, selectServer], +);