Decouple shlink-web-component from the concept of servers

This commit is contained in:
Alejandro Celaya 2023-07-24 18:03:59 +02:00
parent 5f6dc186e3
commit 21525ef945
22 changed files with 55 additions and 82 deletions

View file

@ -3,9 +3,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import type { FC } from 'react';
import { useEffect } from 'react';
import { UncontrolledTooltip } from 'reactstrap';
import type { ShlinkDomainRedirects } from '../../api/types';
import type { SelectedServer } from '../../servers/data';
import type { OptionalString } from '../../utils/utils';
import type { ShlinkDomainRedirects } from '../api-contract';
import type { Domain } from './data';
import { DomainDropdown } from './helpers/DomainDropdown';
import { DomainStatusIcon } from './helpers/DomainStatusIcon';
@ -16,7 +15,6 @@ interface DomainRowProps {
defaultRedirects?: ShlinkDomainRedirects;
editDomainRedirects: (redirects: EditDomainRedirects) => Promise<void>;
checkDomainHealth: (domain: string) => void;
selectedServer: SelectedServer;
}
const Nr: FC<{ fallback: OptionalString }> = ({ fallback }) => (
@ -33,7 +31,7 @@ const DefaultDomain: FC = () => (
);
export const DomainRow: FC<DomainRowProps> = (
{ domain, editDomainRedirects, checkDomainHealth, defaultRedirects, selectedServer },
{ domain, editDomainRedirects, checkDomainHealth, defaultRedirects },
) => {
const { domain: authority, isDefault, redirects, status } = domain;
@ -58,7 +56,7 @@ export const DomainRow: FC<DomainRowProps> = (
<DomainStatusIcon status={status} />
</td>
<td className="responsive-table__cell text-end">
<DomainDropdown domain={domain} editDomainRedirects={editDomainRedirects} selectedServer={selectedServer} />
<DomainDropdown domain={domain} editDomainRedirects={editDomainRedirects} />
</td>
</tr>
);

View file

@ -1,7 +1,6 @@
import type { FC } from 'react';
import { useEffect } from 'react';
import { ShlinkApiError } from '../../api/ShlinkApiError';
import type { SelectedServer } from '../../servers/data';
import { Message } from '../../utils/Message';
import { Result } from '../../utils/Result';
import { SearchField } from '../../utils/SearchField';
@ -16,13 +15,12 @@ interface ManageDomainsProps {
editDomainRedirects: (redirects: EditDomainRedirects) => Promise<void>;
checkDomainHealth: (domain: string) => void;
domainsList: DomainsList;
selectedServer: SelectedServer;
}
const headers = ['', 'Domain', 'Base path redirect', 'Regular 404 redirect', 'Invalid short URL redirect', '', ''];
export const ManageDomains: FC<ManageDomainsProps> = (
{ listDomains, domainsList, filterDomains, editDomainRedirects, checkDomainHealth, selectedServer },
{ listDomains, domainsList, filterDomains, editDomainRedirects, checkDomainHealth },
) => {
const { filteredDomains: domains, defaultRedirects, loading, error, errorData } = domainsList;
const resolvedDefaultRedirects = defaultRedirects ?? domains.find(({ isDefault }) => isDefault)?.redirects;
@ -59,7 +57,6 @@ export const ManageDomains: FC<ManageDomainsProps> = (
editDomainRedirects={editDomainRedirects}
checkDomainHealth={checkDomainHealth}
defaultRedirects={resolvedDefaultRedirects}
selectedServer={selectedServer}
/>
))}
</tbody>

View file

@ -1,4 +1,4 @@
import type { ShlinkDomain } from '../../../api/types';
import type { ShlinkDomain } from '../../api-contract';
export type DomainStatus = 'validating' | 'valid' | 'invalid';

View file

@ -3,11 +3,10 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import type { FC } from 'react';
import { Link } from 'react-router-dom';
import { DropdownItem } from 'reactstrap';
import type { SelectedServer } from '../../../servers/data';
import { getServerId } from '../../../servers/data';
import { useToggle } from '../../../utils/helpers/hooks';
import { RowDropdownBtn } from '../../../utils/RowDropdownBtn';
import { useFeature } from '../../utils/features';
import { useRoutesPrefix } from '../../utils/routesPrefix';
import { DEFAULT_DOMAIN } from '../../visits/reducers/domainVisits';
import type { Domain } from '../data';
import type { EditDomainRedirects } from '../reducers/domainRedirects';
@ -16,22 +15,21 @@ import { EditDomainRedirectsModal } from './EditDomainRedirectsModal';
interface DomainDropdownProps {
domain: Domain;
editDomainRedirects: (redirects: EditDomainRedirects) => Promise<void>;
selectedServer: SelectedServer;
}
export const DomainDropdown: FC<DomainDropdownProps> = ({ domain, editDomainRedirects, selectedServer }) => {
export const DomainDropdown: FC<DomainDropdownProps> = ({ domain, editDomainRedirects }) => {
const [isModalOpen, toggleModal] = useToggle();
const { isDefault } = domain;
const canBeEdited = !isDefault || useFeature('defaultDomainRedirectsEdition');
const withVisits = useFeature('domainVisits');
const serverId = getServerId(selectedServer);
const routesPrefix = useRoutesPrefix();
return (
<RowDropdownBtn>
{withVisits && (
<DropdownItem
tag={Link}
to={`/server/${serverId}/domain/${domain.domain}${domain.isDefault ? `_${DEFAULT_DOMAIN}` : ''}/visits`}
to={`${routesPrefix}/domain/${domain.domain}${domain.isDefault ? `_${DEFAULT_DOMAIN}` : ''}/visits`}
>
<FontAwesomeIcon icon={pieChartIcon} fixedWidth /> Visit stats
</DropdownItem>

View file

@ -13,7 +13,7 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('ManageDomains', () => ManageDomains);
bottle.decorator('ManageDomains', connect(
['domainsList', 'selectedServer'],
['domainsList'],
['listDomains', 'filterDomains', 'editDomainRedirects', 'checkDomainHealth'],
));

View file

@ -2,12 +2,8 @@ import type { FC } from 'react';
import { useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { Card, CardBody, CardHeader, Row } from 'reactstrap';
import type { ShlinkShortUrlsListParams } from '../../api/types';
import type { SelectedServer } from '../../servers/data';
import { getServerId } from '../../servers/data';
import { HighlightCard } from '../../servers/helpers/HighlightCard';
import { VisitsHighlightCard } from '../../servers/helpers/VisitsHighlightCard';
import { prettify } from '../../utils/helpers/numbers';
import type { ShlinkShortUrlsListParams } from '../api-contract';
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
import { Topics } from '../mercure/helpers/Topics';
import type { CreateShortUrlProps } from '../short-urls/CreateShortUrl';
@ -16,15 +12,17 @@ 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 { useFeature } from '../utils/features';
import { useRoutesPrefix } from '../utils/routesPrefix';
import { useSetting } from '../utils/settings';
import type { VisitsOverview } from '../visits/reducers/visitsOverview';
import { HighlightCard } from './helpers/HighlightCard';
import { VisitsHighlightCard } from './helpers/VisitsHighlightCard';
interface OverviewConnectProps {
shortUrlsList: ShortUrlsListState;
listShortUrls: (params: ShlinkShortUrlsListParams) => void;
listTags: Function;
tagsList: TagsList;
selectedServer: SelectedServer;
visitsOverview: VisitsOverview;
loadVisitsOverview: Function;
}
@ -37,14 +35,13 @@ export const Overview = (
listShortUrls,
listTags,
tagsList,
selectedServer,
loadVisitsOverview,
visitsOverview,
}: OverviewConnectProps) => {
const { loading, shortUrls } = shortUrlsList;
const { loading: loadingTags } = tagsList;
const { loading: loadingVisits, nonOrphanVisits, orphanVisits } = visitsOverview;
const serverId = getServerId(selectedServer);
const routesPrefix = useRoutesPrefix();
const linkToNonOrphanVisits = useFeature('nonOrphanVisits');
const navigate = useNavigate();
const visits = useSetting('visits');
@ -61,7 +58,7 @@ export const Overview = (
<div className="col-lg-6 col-xl-3 mb-3">
<VisitsHighlightCard
title="Visits"
link={linkToNonOrphanVisits ? `/server/${serverId}/non-orphan-visits` : undefined}
link={linkToNonOrphanVisits ? `${routesPrefix}/non-orphan-visits` : undefined}
excludeBots={visits?.excludeBots ?? false}
loading={loadingVisits}
visitsSummary={nonOrphanVisits}
@ -70,19 +67,19 @@ export const Overview = (
<div className="col-lg-6 col-xl-3 mb-3">
<VisitsHighlightCard
title="Orphan visits"
link={`/server/${serverId}/orphan-visits`}
link={`${routesPrefix}/orphan-visits`}
excludeBots={visits?.excludeBots ?? false}
loading={loadingVisits}
visitsSummary={orphanVisits}
/>
</div>
<div className="col-lg-6 col-xl-3 mb-3">
<HighlightCard title="Short URLs" link={`/server/${serverId}/list-short-urls/1`}>
<HighlightCard title="Short URLs" link={`${routesPrefix}/list-short-urls/1`}>
{loading ? 'Loading...' : prettify(shortUrls?.pagination.totalItems ?? 0)}
</HighlightCard>
</div>
<div className="col-lg-6 col-xl-3 mb-3">
<HighlightCard title="Tags" link={`/server/${serverId}/manage-tags`}>
<HighlightCard title="Tags" link={`${routesPrefix}/manage-tags`}>
{loadingTags ? 'Loading...' : prettify(tagsList.tags.length)}
</HighlightCard>
</div>
@ -92,7 +89,7 @@ export const Overview = (
<CardHeader>
<span className="d-sm-none">Create a short URL</span>
<h5 className="d-none d-sm-inline">Create a short URL</h5>
<Link className="float-end" to={`/server/${serverId}/create-short-url`}>Advanced options &raquo;</Link>
<Link className="float-end" to={`${routesPrefix}/create-short-url`}>Advanced options &raquo;</Link>
</CardHeader>
<CardBody>
<CreateShortUrl basicMode />
@ -102,13 +99,13 @@ export const Overview = (
<CardHeader>
<span className="d-sm-none">Recently created URLs</span>
<h5 className="d-none d-sm-inline">Recently created URLs</h5>
<Link className="float-end" to={`/server/${serverId}/list-short-urls/1`}>See all &raquo;</Link>
<Link className="float-end" to={`${routesPrefix}/list-short-urls/1`}>See all &raquo;</Link>
</CardHeader>
<CardBody>
<ShortUrlsTable
shortUrlsList={shortUrlsList}
className="mb-0"
onTagClick={(tag) => navigate(`/server/${serverId}/list-short-urls/1?tags=${encodeURIComponent(tag)}`)}
onTagClick={(tag) => navigate(`${routesPrefix}/list-short-urls/1?tags=${encodeURIComponent(tag)}`)}
/>
</CardBody>
</Card>

View file

@ -1,4 +1,4 @@
@import '../../utils/base';
@import '../../../utils/base';
.highlight-card.highlight-card {
text-align: center;

View file

@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import type { FC, PropsWithChildren, ReactNode } from 'react';
import { Link } from 'react-router-dom';
import { Card, CardText, CardTitle, UncontrolledTooltip } from 'reactstrap';
import { useElementRef } from '../../utils/helpers/hooks';
import { useElementRef } from '../../../utils/helpers/hooks';
import './HighlightCard.scss';
export type HighlightCardProps = PropsWithChildren<{

View file

@ -1,6 +1,6 @@
import type { FC } from 'react';
import type { PartialVisitsSummary } from '../../shlink-web-component/visits/reducers/visitsOverview';
import { prettify } from '../../utils/helpers/numbers';
import { prettify } from '../../../utils/helpers/numbers';
import type { PartialVisitsSummary } from '../../visits/reducers/visitsOverview';
import type { HighlightCardProps } from './HighlightCard';
import { HighlightCard } from './HighlightCard';

View file

@ -5,7 +5,7 @@ import { Overview } from '../Overview';
export function provideServices(bottle: Bottle, connect: ConnectDecorator) {
bottle.serviceFactory('Overview', Overview, 'ShortUrlsTable', 'CreateShortUrl');
bottle.decorator('Overview', connect(
['shortUrlsList', 'tagsList', 'selectedServer', 'mercureInfo', 'visitsOverview'],
['shortUrlsList', 'tagsList', 'mercureInfo', 'visitsOverview'],
['listShortUrls', 'listTags', 'createNewVisits', 'loadMercureInfo', 'loadVisitsOverview'],
));
}

View file

@ -1,8 +1,6 @@
import type { FC } from 'react';
import { useCallback } from 'react';
import type { ReportExporter } from '../../../common/services/ReportExporter';
import type { SelectedServer } from '../../../servers/data';
import { isServerWithId } from '../../../servers/data';
import { ExportBtn } from '../../../utils/ExportBtn';
import { useToggle } from '../../../utils/helpers/hooks';
import type { ShlinkApiClient } from '../../api-contract';
@ -13,23 +11,15 @@ export interface ExportShortUrlsBtnProps {
amount?: number;
}
interface ExportShortUrlsBtnConnectProps extends ExportShortUrlsBtnProps {
selectedServer: SelectedServer;
}
const itemsPerPage = 20;
export const ExportShortUrlsBtn = (
apiClient: ShlinkApiClient,
{ exportShortUrls }: ReportExporter,
): FC<ExportShortUrlsBtnConnectProps> => ({ amount = 0, selectedServer }) => {
): FC<ExportShortUrlsBtnProps> => ({ amount = 0 }) => {
const [{ tags, search, startDate, endDate, orderBy, tagsMode }] = useShortUrlsQuery();
const [loading,, startLoading, stopLoading] = useToggle();
const exportAllUrls = useCallback(async () => {
if (!isServerWithId(selectedServer)) {
return;
}
const totalPages = amount / itemsPerPage;
const loadAllUrls = async (page = 1): Promise<ShortUrl[]> => {
const { data } = await apiClient.listShortUrls(
@ -63,7 +53,7 @@ export const ExportShortUrlsBtn = (
};
}));
stopLoading();
}, [selectedServer]);
}, []);
return <ExportBtn loading={loading} className="btn-md-block" amount={amount} onClick={exportAllUrls} />;
};

View file

@ -1,11 +1,12 @@
import { isEmpty, pipe } from 'ramda';
import { useMemo } from 'react';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import type { TagsFilteringMode } from '../../../api/types';
import { useLocation, useNavigate } from 'react-router-dom';
import { orderToString, stringToOrder } from '../../../utils/helpers/ordering';
import { parseQuery, stringifyQuery } from '../../../utils/helpers/query';
import type { BooleanString } from '../../../utils/utils';
import { parseOptionalBooleanToString } from '../../../utils/utils';
import type { TagsFilteringMode } from '../../api-contract';
import { useRoutesPrefix } from '../../utils/routesPrefix';
import type { ShortUrlsOrder, ShortUrlsOrderableFields } from '../data';
interface ShortUrlsQueryCommon {
@ -36,7 +37,7 @@ type ToFirstPage = (extra: Partial<ShortUrlsFiltering>) => void;
export const useShortUrlsQuery = (): [ShortUrlsFiltering, ToFirstPage] => {
const navigate = useNavigate();
const { search } = useLocation();
const { serverId = '' } = useParams<{ serverId: string }>();
const routesPrefix = useRoutesPrefix();
const filtering = useMemo(
pipe(
@ -70,7 +71,7 @@ export const useShortUrlsQuery = (): [ShortUrlsFiltering, ToFirstPage] => {
const stringifiedQuery = stringifyQuery(query);
const queryString = isEmpty(stringifiedQuery) ? '' : `?${stringifiedQuery}`;
navigate(`/server/${serverId}/list-short-urls/1${queryString}`);
navigate(`${routesPrefix}/list-short-urls/1${queryString}`);
};
return [filtering, toFirstPageWithExtra];

View file

@ -55,7 +55,6 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('ShortUrlsFilteringBar', ShortUrlsFilteringBar, 'ExportShortUrlsBtn', 'TagsSelector');
bottle.serviceFactory('ExportShortUrlsBtn', ExportShortUrlsBtn, 'apiClient', 'ReportExporter');
bottle.decorator('ExportShortUrlsBtn', connect(['selectedServer']));
// Reducers
bottle.serviceFactory(

View file

@ -3,7 +3,6 @@ import type { FC } from 'react';
import { useEffect, useState } from 'react';
import { Row } from 'reactstrap';
import { ShlinkApiError } from '../../api/ShlinkApiError';
import type { SelectedServer } from '../../servers/data';
import { determineOrderDir, sortList } from '../../utils/helpers/ordering';
import { Message } from '../../utils/Message';
import { OrderingDropdown } from '../../utils/OrderingDropdown';
@ -22,11 +21,10 @@ export interface TagsListProps {
filterTags: (searchTerm: string) => void;
forceListTags: Function;
tagsList: TagsListState;
selectedServer: SelectedServer;
}
export const TagsList = (TagsTable: FC<TagsTableProps>) => boundToMercureHub((
{ filterTags, forceListTags, tagsList, selectedServer }: TagsListProps,
{ filterTags, forceListTags, tagsList }: TagsListProps,
) => {
const settings = useSettings();
const [order, setOrder] = useState<TagsOrder>(settings.tags?.defaultOrdering ?? {});
@ -78,7 +76,6 @@ export const TagsList = (TagsTable: FC<TagsTableProps>) => boundToMercureHub((
return (
<TagsTable
sortedTags={sortedTags}
selectedServer={selectedServer}
currentOrder={order}
orderByColumn={orderByColumn}
/>

View file

@ -19,7 +19,7 @@ export interface TagsTableProps extends TagsListChildrenProps {
const TAGS_PER_PAGE = 20; // TODO Allow customizing this value in settings
export const TagsTable = (TagsTableRow: FC<TagsTableRowProps>) => (
{ sortedTags, selectedServer, orderByColumn, currentOrder }: TagsTableProps,
{ sortedTags, orderByColumn, currentOrder }: TagsTableProps,
) => {
const isFirstLoad = useRef(true);
const { search } = useLocation();
@ -57,7 +57,7 @@ export const TagsTable = (TagsTableRow: FC<TagsTableRowProps>) => (
</thead>
<tbody>
{currentPage.length === 0 && <tr><td colSpan={4} className="text-center">No results found</td></tr>}
{currentPage.map((tag) => <TagsTableRow key={tag.tag} tag={tag} selectedServer={selectedServer} />)}
{currentPage.map((tag) => <TagsTableRow key={tag.tag} tag={tag} />)}
</tbody>
</table>

View file

@ -3,28 +3,26 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import type { FC } from 'react';
import { Link } from 'react-router-dom';
import { DropdownItem } from 'reactstrap';
import type { SelectedServer } from '../../servers/data';
import { getServerId } from '../../servers/data';
import { useToggle } from '../../utils/helpers/hooks';
import { prettify } from '../../utils/helpers/numbers';
import { RowDropdownBtn } from '../../utils/RowDropdownBtn';
import type { ColorGenerator } from '../../utils/services/ColorGenerator';
import { useRoutesPrefix } from '../utils/routesPrefix';
import type { SimplifiedTag, TagModalProps } from './data';
import { TagBullet } from './helpers/TagBullet';
export interface TagsTableRowProps {
tag: SimplifiedTag;
selectedServer: SelectedServer;
}
export const TagsTableRow = (
DeleteTagConfirmModal: FC<TagModalProps>,
EditTagModal: FC<TagModalProps>,
colorGenerator: ColorGenerator,
) => ({ tag, selectedServer }: TagsTableRowProps) => {
) => ({ tag }: TagsTableRowProps) => {
const [isDeleteModalOpen, toggleDelete] = useToggle();
const [isEditModalOpen, toggleEdit] = useToggle();
const serverId = getServerId(selectedServer);
const routesPrefix = useRoutesPrefix();
return (
<tr className="responsive-table__row">
@ -32,12 +30,12 @@ export const TagsTableRow = (
<TagBullet tag={tag.tag} colorGenerator={colorGenerator} /> {tag.tag}
</th>
<td className="responsive-table__cell text-lg-end" data-th="Short URLs">
<Link to={`/server/${serverId}/list-short-urls/1?tags=${encodeURIComponent(tag.tag)}`}>
<Link to={`${routesPrefix}/list-short-urls/1?tags=${encodeURIComponent(tag.tag)}`}>
{prettify(tag.shortUrls)}
</Link>
</td>
<td className="responsive-table__cell text-lg-end" data-th="Visits">
<Link to={`/server/${serverId}/tag/${tag.tag}/visits`}>
<Link to={`${routesPrefix}/tag/${tag.tag}/visits`}>
{prettify(tag.visits)}
</Link>
</td>

View file

@ -1,4 +1,3 @@
import type { SelectedServer } from '../../../servers/data';
import type { Order } from '../../../utils/helpers/ordering';
import type { SimplifiedTag } from './index';
@ -14,5 +13,4 @@ export type TagsOrder = Order<TagsOrderableFields>;
export interface TagsListChildrenProps {
sortedTags: SimplifiedTag[];
selectedServer: SelectedServer;
}

View file

@ -1,11 +1,9 @@
import { createAction, createSlice } from '@reduxjs/toolkit';
import { isEmpty, reject } from 'ramda';
import { isReachableServer } from '../../../servers/data';
import { createAsyncThunk } from '../../../utils/helpers/redux';
import type { ProblemDetailsError, ShlinkApiClient, ShlinkTags } from '../../api-contract';
import { parseApiError } from '../../api-contract/utils';
import type { createShortUrl } from '../../short-urls/reducers/shortUrlCreation';
import { isFeatureEnabledForVersion } from '../../utils/features';
import { createNewVisits } from '../../visits/reducers/visitCreation';
import type { CreateVisit } from '../../visits/types';
import type { TagStats } from '../data';
@ -85,17 +83,13 @@ const calculateVisitsPerTag = (createdVisits: CreateVisit[]): TagIncrease[] => O
export const listTags = (apiClient: ShlinkApiClient, force = true) => createAsyncThunk(
`${REDUCER_PREFIX}/listTags`,
async (_: void, { getState }): Promise<ListTags> => {
const { tagsList, selectedServer } = getState();
const { tagsList } = getState();
if (!force && !isEmpty(tagsList.tags)) {
return tagsList;
}
const { tags, stats }: ShlinkTags = await (
isReachableServer(selectedServer) && isFeatureEnabledForVersion('tagsStats', selectedServer.version)
? apiClient.tagsStats()
: apiClient.listTags()
);
const { tags, stats }: ShlinkTags = await apiClient.tagsStats();
const processedStats = stats.reduce<TagsStatsMap>((acc, { tag, ...rest }) => {
acc[tag] = rest;
return acc;

View file

@ -28,7 +28,7 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('TagsList', TagsList, 'TagsTable');
bottle.decorator('TagsList', connect(
['tagsList', 'selectedServer', 'mercureInfo'],
['tagsList', 'mercureInfo'],
['forceListTags', 'filterTags', 'createNewVisits', 'loadMercureInfo'],
));

View file

@ -3,12 +3,15 @@ import type { SemVer } from '../../utils/helpers/version';
import { versionMatch } from '../../utils/helpers/version';
const supportedFeatures = {
// Deprecated
forwardQuery: '2.9.0',
nonRestCors: '2.9.0',
defaultDomainRedirectsEdition: '2.10.0',
nonOrphanVisits: '3.0.0',
allTagsFiltering: '3.0.0',
tagsStats: '3.0.0',
// End deprecated
domainVisits: '3.1.0',
excludeBotsOnShortUrls: '3.4.0',
filterDisabledUrls: '3.4.0',
@ -23,12 +26,15 @@ export const isFeatureEnabledForVersion = (feature: Feature, serverVersion: SemV
versionMatch(serverVersion, { minVersion: supportedFeatures[feature] });
const getFeaturesForVersion = (serverVersion: SemVer): Record<Feature, boolean> => ({
// Deprecated
forwardQuery: isFeatureEnabledForVersion('forwardQuery', serverVersion),
nonRestCors: isFeatureEnabledForVersion('nonRestCors', serverVersion),
defaultDomainRedirectsEdition: isFeatureEnabledForVersion('defaultDomainRedirectsEdition', serverVersion),
nonOrphanVisits: isFeatureEnabledForVersion('nonOrphanVisits', serverVersion),
allTagsFiltering: isFeatureEnabledForVersion('allTagsFiltering', serverVersion),
tagsStats: isFeatureEnabledForVersion('tagsStats', serverVersion),
// End
domainVisits: isFeatureEnabledForVersion('domainVisits', serverVersion),
excludeBotsOnShortUrls: isFeatureEnabledForVersion('excludeBotsOnShortUrls', serverVersion),
filterDisabledUrls: isFeatureEnabledForVersion('filterDisabledUrls', serverVersion),

View file

@ -1,8 +1,8 @@
import { screen, waitFor } from '@testing-library/react';
import type { ReactNode } from 'react';
import { MemoryRouter } from 'react-router-dom';
import type { HighlightCardProps } from '../../../src/servers/helpers/HighlightCard';
import { HighlightCard } from '../../../src/servers/helpers/HighlightCard';
import type { HighlightCardProps } from '../../../src/shlink-web-component/overview/helpers/HighlightCard';
import { HighlightCard } from '../../../src/shlink-web-component/overview/helpers/HighlightCard';
import { renderWithEvents } from '../../__helpers__/setUpTest';
describe('<HighlightCard />', () => {

View file

@ -1,6 +1,6 @@
import { screen, waitFor } from '@testing-library/react';
import type { VisitsHighlightCardProps } from '../../../src/servers/helpers/VisitsHighlightCard';
import { VisitsHighlightCard } from '../../../src/servers/helpers/VisitsHighlightCard';
import type { VisitsHighlightCardProps } from '../../../src/shlink-web-component/overview/helpers/VisitsHighlightCard';
import { VisitsHighlightCard } from '../../../src/shlink-web-component/overview/helpers/VisitsHighlightCard';
import { renderWithEvents } from '../../__helpers__/setUpTest';
describe('<VisitsHighlightCard />', () => {