mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-08 17:27:32 +03:00
Added filtering dropdown to short URLs filtering bar
This commit is contained in:
parent
b00f6fadf8
commit
e790360de9
4 changed files with 47 additions and 11 deletions
|
@ -8,7 +8,7 @@ import { SearchField } from '../utils/SearchField';
|
|||
import { DateRangeSelector } from '../utils/dates/DateRangeSelector';
|
||||
import { formatIsoDate } from '../utils/helpers/date';
|
||||
import { DateRange, datesToDateRange } from '../utils/helpers/dateIntervals';
|
||||
import { supportsAllTagsFiltering } from '../utils/helpers/features';
|
||||
import { supportsAllTagsFiltering, supportsBotVisits } from '../utils/helpers/features';
|
||||
import { SelectedServer } from '../servers/data';
|
||||
import { OrderDir } from '../utils/helpers/ordering';
|
||||
import { OrderingDropdown } from '../utils/OrderingDropdown';
|
||||
|
@ -16,11 +16,14 @@ import { useShortUrlsQuery } from './helpers/hooks';
|
|||
import { SHORT_URLS_ORDERABLE_FIELDS, ShortUrlsOrder, ShortUrlsOrderableFields } from './data';
|
||||
import { ExportShortUrlsBtnProps } from './helpers/ExportShortUrlsBtn';
|
||||
import { TagsSelectorProps } from '../tags/helpers/TagsSelector';
|
||||
import { ShortUrlsFilterDropdown } from './helpers/ShortUrlsFilterDropdown';
|
||||
import { Settings } from '../settings/reducers/settings';
|
||||
import './ShortUrlsFilteringBar.scss';
|
||||
|
||||
interface ShortUrlsFilteringProps {
|
||||
selectedServer: SelectedServer;
|
||||
order: ShortUrlsOrder;
|
||||
settings: Settings;
|
||||
handleOrderBy: (orderField?: ShortUrlsOrderableFields, orderDir?: OrderDir) => void;
|
||||
className?: string;
|
||||
shortUrlsAmount?: number;
|
||||
|
@ -29,8 +32,8 @@ interface ShortUrlsFilteringProps {
|
|||
export const ShortUrlsFilteringBar = (
|
||||
ExportShortUrlsBtn: FC<ExportShortUrlsBtnProps>,
|
||||
TagsSelector: FC<TagsSelectorProps>,
|
||||
): FC<ShortUrlsFilteringProps> => ({ selectedServer, className, shortUrlsAmount, order, handleOrderBy }) => {
|
||||
const [{ search, tags, startDate, endDate, tagsMode = 'any' }, toFirstPage] = useShortUrlsQuery();
|
||||
): FC<ShortUrlsFilteringProps> => ({ selectedServer, className, shortUrlsAmount, order, handleOrderBy, settings }) => {
|
||||
const [{ search, tags, startDate, endDate, excludeBots, tagsMode = 'any' }, toFirstPage] = useShortUrlsQuery();
|
||||
const setDates = pipe(
|
||||
({ startDate: theStartDate, endDate: theEndDate }: DateRange) => ({
|
||||
startDate: formatIsoDate(theStartDate) ?? undefined,
|
||||
|
@ -44,6 +47,7 @@ export const ShortUrlsFilteringBar = (
|
|||
);
|
||||
const changeTagSelection = (selectedTags: string[]) => toFirstPage({ tags: selectedTags });
|
||||
const canChangeTagsMode = supportsAllTagsFiltering(selectedServer);
|
||||
const botsSupported = supportsBotVisits(selectedServer);
|
||||
const toggleTagsMode = pipe(
|
||||
() => (tagsMode === 'any' ? 'all' : 'any'),
|
||||
(mode) => toFirstPage({ tagsMode: mode }),
|
||||
|
@ -69,11 +73,21 @@ export const ShortUrlsFilteringBar = (
|
|||
|
||||
<Row className="flex-lg-row-reverse">
|
||||
<div className="col-lg-8 col-xl-6 mt-3">
|
||||
<DateRangeSelector
|
||||
defaultText="All short URLs"
|
||||
initialDateRange={datesToDateRange(startDate, endDate)}
|
||||
onDatesChange={setDates}
|
||||
/>
|
||||
<div className="d-md-flex">
|
||||
<div className="flex-fill">
|
||||
<DateRangeSelector
|
||||
defaultText="All short URLs"
|
||||
initialDateRange={datesToDateRange(startDate, endDate)}
|
||||
onDatesChange={setDates}
|
||||
/>
|
||||
</div>
|
||||
<ShortUrlsFilterDropdown
|
||||
className="ms-0 ms-md-2 mt-3 mt-md-0"
|
||||
botsSupported={botsSupported}
|
||||
selected={{ excludeBots: excludeBots ?? settings.visits?.excludeBots }}
|
||||
onChange={toFirstPage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-6 col-lg-4 col-xl-6 mt-3">
|
||||
<ExportShortUrlsBtn amount={shortUrlsAmount} />
|
||||
|
|
|
@ -31,12 +31,13 @@ export const ShortUrlsList = (
|
|||
const serverId = getServerId(selectedServer);
|
||||
const { page } = useParams();
|
||||
const location = useLocation();
|
||||
const [{ tags, search, startDate, endDate, orderBy, tagsMode }, toFirstPage] = useShortUrlsQuery();
|
||||
const [{ tags, search, startDate, endDate, orderBy, tagsMode, excludeBots }, toFirstPage] = useShortUrlsQuery();
|
||||
const [actualOrderBy, setActualOrderBy] = useState(
|
||||
// This separated state handling is needed to be able to fall back to settings value, but only once when loaded
|
||||
orderBy ?? settings.shortUrlsList?.defaultOrdering ?? DEFAULT_SHORT_URLS_ORDERING,
|
||||
);
|
||||
const { pagination } = shortUrlsList?.shortUrls ?? {};
|
||||
const doExcludeBots = excludeBots ?? settings.visits?.excludeBots;
|
||||
const handleOrderBy = (field?: ShortUrlsOrderableFields, dir?: OrderDir) => {
|
||||
toFirstPage({ orderBy: { field, dir } });
|
||||
setActualOrderBy({ field, dir });
|
||||
|
@ -50,7 +51,7 @@ export const ShortUrlsList = (
|
|||
(updatedTags) => toFirstPage({ tags: updatedTags }),
|
||||
);
|
||||
const parseOrderByForShlink = ({ field, dir }: ShortUrlsOrder): ShlinkShortUrlsOrder => {
|
||||
if (supportsExcludeBotsOnShortUrls(selectedServer) && settings.visits?.excludeBots && field === 'visits') {
|
||||
if (supportsExcludeBotsOnShortUrls(selectedServer) && doExcludeBots && field === 'visits') {
|
||||
return { field: 'nonBotVisits', dir };
|
||||
}
|
||||
|
||||
|
@ -76,6 +77,7 @@ export const ShortUrlsList = (
|
|||
shortUrlsAmount={shortUrlsList.shortUrls?.pagination.totalItems}
|
||||
order={actualOrderBy}
|
||||
handleOrderBy={handleOrderBy}
|
||||
settings={settings}
|
||||
className="mb-3"
|
||||
/>
|
||||
<Card body className="pb-0">
|
||||
|
|
|
@ -52,7 +52,7 @@ export const ExportShortUrlsBtn = (
|
|||
longUrl: shortUrl.longUrl,
|
||||
title: shortUrl.title ?? '',
|
||||
tags: shortUrl.tags.join(','),
|
||||
visits: shortUrl.visitsCount,
|
||||
visits: shortUrl?.visitsSummary?.total ?? shortUrl.visitsCount,
|
||||
})));
|
||||
stopLoading();
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ import { endOfDay, formatISO, startOfDay } from 'date-fns';
|
|||
import { MemoryRouter, useLocation, useNavigate } from 'react-router-dom';
|
||||
import { ShortUrlsFilteringBar as filteringBarCreator } from '../../src/short-urls/ShortUrlsFilteringBar';
|
||||
import { ReachableServer, SelectedServer } from '../../src/servers/data';
|
||||
import { Settings } from '../../src/settings/reducers/settings';
|
||||
import { DateRange } from '../../src/utils/helpers/dateIntervals';
|
||||
import { formatDate } from '../../src/utils/helpers/date';
|
||||
import { renderWithEvents } from '../__helpers__/setUpTest';
|
||||
|
@ -30,6 +31,7 @@ describe('<ShortUrlsFilteringBar />', () => {
|
|||
selectedServer={selectedServer ?? Mock.all<SelectedServer>()}
|
||||
order={{}}
|
||||
handleOrderBy={handleOrderBy}
|
||||
settings={Mock.of<Settings>({ visits: {} })}
|
||||
/>
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
@ -114,6 +116,24 @@ describe('<ShortUrlsFilteringBar />', () => {
|
|||
expect(navigate).toHaveBeenCalledWith(expect.stringContaining(expectedRedirectTagsMode));
|
||||
});
|
||||
|
||||
it.each([
|
||||
['', 'excludeBots=true'],
|
||||
['excludeBots=false', 'excludeBots=true'],
|
||||
['excludeBots=true', 'excludeBots=false'],
|
||||
])('allows to toggle excluding bots through filtering dropdown', async (search, expectedQuery) => {
|
||||
const { user } = setUp(
|
||||
search,
|
||||
Mock.of<ReachableServer>({ version: '3.4.0' }),
|
||||
);
|
||||
const toggleBots = async (name = 'Exclude bots visits') => {
|
||||
await user.click(screen.getByRole('button', { name: 'Filters' }));
|
||||
await user.click(await screen.findByRole('menuitem', { name }));
|
||||
};
|
||||
|
||||
await toggleBots();
|
||||
expect(navigate).toHaveBeenCalledWith(expect.stringContaining(expectedQuery));
|
||||
});
|
||||
|
||||
it('handles order through dropdown', async () => {
|
||||
const { user } = setUp();
|
||||
const clickMenuItem = async (name: string | RegExp) => {
|
||||
|
|
Loading…
Reference in a new issue