Merge pull request #811 from acelaya-forks/feature/feature-flag-hooks

Convert feature flags into hooks
This commit is contained in:
Alejandro Celaya 2023-03-11 10:38:26 +01:00 committed by GitHub
commit fa69c21fa2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 41 additions and 26 deletions

View file

@ -6,7 +6,7 @@ import { useEffect } from 'react';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom'; import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import { isReachableServer } from '../servers/data'; import { isReachableServer } from '../servers/data';
import { withSelectedServer } from '../servers/helpers/withSelectedServer'; 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 { useSwipeable, useToggle } from '../utils/helpers/hooks';
import type { AsideMenuProps } from './AsideMenu'; import type { AsideMenuProps } from './AsideMenu';
import { NotFound } from './NotFound'; import { NotFound } from './NotFound';
@ -46,8 +46,8 @@ export const MenuLayout = (
return <ServerError />; return <ServerError />;
} }
const addNonOrphanVisitsRoute = supportsNonOrphanVisits(selectedServer); const addNonOrphanVisitsRoute = useFeature('nonOrphanVisits', selectedServer);
const addDomainVisitsRoute = supportsDomainVisits(selectedServer); const addDomainVisitsRoute = useFeature('domainVisits', selectedServer);
const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible }); const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible });
const swipeableProps = useSwipeable(showSidebar, hideSidebar); const swipeableProps = useSwipeable(showSidebar, hideSidebar);

View file

@ -6,7 +6,7 @@ import { DropdownItem } from 'reactstrap';
import type { SelectedServer } from '../../servers/data'; import type { SelectedServer } from '../../servers/data';
import { getServerId } from '../../servers/data'; import { getServerId } from '../../servers/data';
import { DropdownBtnMenu } from '../../utils/DropdownBtnMenu'; 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 { useToggle } from '../../utils/helpers/hooks';
import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits'; import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits';
import type { Domain } from '../data'; import type { Domain } from '../data';
@ -23,8 +23,8 @@ export const DomainDropdown: FC<DomainDropdownProps> = ({ domain, editDomainRedi
const [isOpen, toggle] = useToggle(); const [isOpen, toggle] = useToggle();
const [isModalOpen, toggleModal] = useToggle(); const [isModalOpen, toggleModal] = useToggle();
const { isDefault } = domain; const { isDefault } = domain;
const canBeEdited = !isDefault || supportsDefaultDomainRedirectsEdition(selectedServer); const canBeEdited = !isDefault || useFeature('defaultDomainRedirectsEdition', selectedServer);
const withVisits = supportsDomainVisits(selectedServer); const withVisits = useFeature('domainVisits', selectedServer);
const serverId = getServerId(selectedServer); const serverId = getServerId(selectedServer);
return ( return (

View file

@ -10,7 +10,7 @@ import type { ShortUrlsList as ShortUrlsListState } from '../short-urls/reducers
import { ITEMS_IN_OVERVIEW_PAGE } from '../short-urls/reducers/shortUrlsList'; import { ITEMS_IN_OVERVIEW_PAGE } from '../short-urls/reducers/shortUrlsList';
import type { ShortUrlsTableType } from '../short-urls/ShortUrlsTable'; import type { ShortUrlsTableType } from '../short-urls/ShortUrlsTable';
import type { TagsList } from '../tags/reducers/tagsList'; 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 { prettify } from '../utils/helpers/numbers';
import type { VisitsOverview } from '../visits/reducers/visitsOverview'; import type { VisitsOverview } from '../visits/reducers/visitsOverview';
import type { SelectedServer } from './data'; import type { SelectedServer } from './data';
@ -43,7 +43,7 @@ export const Overview = (
const { loading: loadingTags } = tagsList; const { loading: loadingTags } = tagsList;
const { loading: loadingVisits, visitsCount, orphanVisitsCount } = visitsOverview; const { loading: loadingVisits, visitsCount, orphanVisitsCount } = visitsOverview;
const serverId = getServerId(selectedServer); const serverId = getServerId(selectedServer);
const linkToNonOrphanVisits = supportsNonOrphanVisits(selectedServer); const linkToNonOrphanVisits = useFeature('nonOrphanVisits', selectedServer);
const navigate = useNavigate(); const navigate = useNavigate();
useEffect(() => { useEffect(() => {

View file

@ -11,7 +11,7 @@ import { Checkbox } from '../utils/Checkbox';
import type { DateTimeInputProps } from '../utils/dates/DateTimeInput'; import type { DateTimeInputProps } from '../utils/dates/DateTimeInput';
import { DateTimeInput } from '../utils/dates/DateTimeInput'; import { DateTimeInput } from '../utils/dates/DateTimeInput';
import { formatIsoDate } from '../utils/helpers/date'; import { formatIsoDate } from '../utils/helpers/date';
import { supportsForwardQuery } from '../utils/helpers/features'; import { useFeature } from '../utils/helpers/features';
import { SimpleCard } from '../utils/SimpleCard'; import { SimpleCard } from '../utils/SimpleCard';
import type { OptionalString } from '../utils/utils'; import type { OptionalString } from '../utils/utils';
import { handleEventPreventingDefault, hasValue } 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 ( return (
<form name="shortUrlForm" className="short-url-form" onSubmit={submit}> <form name="shortUrlForm" className="short-url-form" onSubmit={submit}>

View file

@ -11,7 +11,7 @@ import { DateRangeSelector } from '../utils/dates/DateRangeSelector';
import { formatIsoDate } from '../utils/helpers/date'; import { formatIsoDate } from '../utils/helpers/date';
import type { DateRange } from '../utils/helpers/dateIntervals'; import type { DateRange } from '../utils/helpers/dateIntervals';
import { datesToDateRange } 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 type { OrderDir } from '../utils/helpers/ordering';
import { OrderingDropdown } from '../utils/OrderingDropdown'; import { OrderingDropdown } from '../utils/OrderingDropdown';
import { SearchField } from '../utils/SearchField'; import { SearchField } from '../utils/SearchField';
@ -46,7 +46,7 @@ export const ShortUrlsFilteringBar = (
excludePastValidUntil, excludePastValidUntil,
tagsMode = 'any', tagsMode = 'any',
} = filter; } = filter;
const supportsDisabledFiltering = supportsFilterDisabledUrls(selectedServer); const supportsDisabledFiltering = useFeature('filterDisabledUrls', selectedServer);
const setDates = pipe( const setDates = pipe(
({ startDate: theStartDate, endDate: theEndDate }: DateRange) => ({ ({ startDate: theStartDate, endDate: theEndDate }: DateRange) => ({
@ -60,7 +60,7 @@ export const ShortUrlsFilteringBar = (
(searchTerm) => toFirstPage({ search: searchTerm }), (searchTerm) => toFirstPage({ search: searchTerm }),
); );
const changeTagSelection = (selectedTags: string[]) => toFirstPage({ tags: selectedTags }); const changeTagSelection = (selectedTags: string[]) => toFirstPage({ tags: selectedTags });
const canChangeTagsMode = supportsAllTagsFiltering(selectedServer); const canChangeTagsMode = useFeature('allTagsFiltering', selectedServer);
const toggleTagsMode = pipe( const toggleTagsMode = pipe(
() => (tagsMode === 'any' ? 'all' : 'any'), () => (tagsMode === 'any' ? 'all' : 'any'),
(mode) => toFirstPage({ tagsMode: mode }), (mode) => toFirstPage({ tagsMode: mode }),

View file

@ -9,7 +9,7 @@ import type { SelectedServer } from '../servers/data';
import { getServerId } from '../servers/data'; import { getServerId } from '../servers/data';
import type { Settings } from '../settings/reducers/settings'; import type { Settings } from '../settings/reducers/settings';
import { DEFAULT_SHORT_URLS_ORDERING } 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 type { OrderDir } from '../utils/helpers/ordering';
import { determineOrderDir } from '../utils/helpers/ordering'; import { determineOrderDir } from '../utils/helpers/ordering';
import { TableOrderIcon } from '../utils/table/TableOrderIcon'; import { TableOrderIcon } from '../utils/table/TableOrderIcon';
@ -52,6 +52,7 @@ export const ShortUrlsList = (
); );
const { pagination } = shortUrlsList?.shortUrls ?? {}; const { pagination } = shortUrlsList?.shortUrls ?? {};
const doExcludeBots = excludeBots ?? settings.visits?.excludeBots; const doExcludeBots = excludeBots ?? settings.visits?.excludeBots;
const supportsExcludingBots = useFeature('excludeBotsOnShortUrls', selectedServer);
const handleOrderBy = (field?: ShortUrlsOrderableFields, dir?: OrderDir) => { const handleOrderBy = (field?: ShortUrlsOrderableFields, dir?: OrderDir) => {
toFirstPage({ orderBy: { field, dir } }); toFirstPage({ orderBy: { field, dir } });
setActualOrderBy({ field, dir }); setActualOrderBy({ field, dir });
@ -65,7 +66,7 @@ export const ShortUrlsList = (
(updatedTags) => toFirstPage({ tags: updatedTags }), (updatedTags) => toFirstPage({ tags: updatedTags }),
); );
const parseOrderByForShlink = ({ field, dir }: ShortUrlsOrder): ShlinkShortUrlsOrder => { const parseOrderByForShlink = ({ field, dir }: ShortUrlsOrder): ShlinkShortUrlsOrder => {
if (supportsExcludeBotsOnShortUrls(selectedServer) && doExcludeBots && field === 'visits') { if (supportsExcludingBots && doExcludeBots && field === 'visits') {
return { field: 'nonBotVisits', dir }; return { field: 'nonBotVisits', dir };
} }

View file

@ -6,7 +6,7 @@ import { Button, FormGroup, Modal, ModalBody, ModalHeader, Row } from 'reactstra
import type { ImageDownloader } from '../../common/services/ImageDownloader'; import type { ImageDownloader } from '../../common/services/ImageDownloader';
import type { SelectedServer } from '../../servers/data'; import type { SelectedServer } from '../../servers/data';
import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon'; 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 type { QrCodeFormat, QrErrorCorrection } from '../../utils/helpers/qrCodes';
import { buildQrCodeUrl } from '../../utils/helpers/qrCodes'; import { buildQrCodeUrl } from '../../utils/helpers/qrCodes';
import type { ShortUrlModalProps } from '../data'; import type { ShortUrlModalProps } from '../data';
@ -25,7 +25,7 @@ export const QrCodeModal = (imageDownloader: ImageDownloader) => (
const [margin, setMargin] = useState(0); const [margin, setMargin] = useState(0);
const [format, setFormat] = useState<QrCodeFormat>('png'); const [format, setFormat] = useState<QrCodeFormat>('png');
const [errorCorrection, setErrorCorrection] = useState<QrErrorCorrection>('L'); const [errorCorrection, setErrorCorrection] = useState<QrErrorCorrection>('L');
const displayDownloadBtn = supportsNonRestCors(selectedServer); const displayDownloadBtn = useFeature('nonRestCors', selectedServer);
const qrCodeUrl = useMemo( const qrCodeUrl = useMemo(
() => buildQrCodeUrl(shortUrl, { size, format, margin, errorCorrection }), () => buildQrCodeUrl(shortUrl, { size, format, margin, errorCorrection }),
[shortUrl, size, format, margin, errorCorrection], [shortUrl, size, format, margin, errorCorrection],

View file

@ -1,16 +1,30 @@
import { useMemo } from 'react';
import type { SelectedServer } from '../../servers/data'; import type { SelectedServer } from '../../servers/data';
import { isReachableServer } from '../../servers/data'; import { isReachableServer } from '../../servers/data';
import { selectServer } from '../../servers/reducers/selectedServer';
import type { SemVerPattern } from './version'; import type { SemVerPattern } from './version';
import { versionMatch } 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 }); isReachableServer(selectedServer) && versionMatch(selectedServer.version, { minVersion });
export const supportsForwardQuery = serverMatchesMinVersion('2.9.0'); export const supportedFeatures = {
export const supportsNonRestCors = supportsForwardQuery; forwardQuery: matchesMinVersion('2.9.0'),
export const supportsDefaultDomainRedirectsEdition = serverMatchesMinVersion('2.10.0'); nonRestCors: matchesMinVersion('2.9.0'),
export const supportsNonOrphanVisits = serverMatchesMinVersion('3.0.0'); defaultDomainRedirectsEdition: matchesMinVersion('2.10.0'),
export const supportsAllTagsFiltering = supportsNonOrphanVisits; nonOrphanVisits: matchesMinVersion('3.0.0'),
export const supportsDomainVisits = serverMatchesMinVersion('3.1.0'); allTagsFiltering: matchesMinVersion('3.0.0'),
export const supportsExcludeBotsOnShortUrls = serverMatchesMinVersion('3.4.0'); domainVisits: matchesMinVersion('3.1.0'),
export const supportsFilterDisabledUrls = supportsExcludeBotsOnShortUrls; 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],
);