diff --git a/src/short-urls/ShortUrlsFilteringBar.tsx b/src/short-urls/ShortUrlsFilteringBar.tsx index b49280f7..fa7cb901 100644 --- a/src/short-urls/ShortUrlsFilteringBar.tsx +++ b/src/short-urls/ShortUrlsFilteringBar.tsx @@ -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, TagsSelector: FC, -): FC => ({ selectedServer, className, shortUrlsAmount, order, handleOrderBy }) => { - const [{ search, tags, startDate, endDate, tagsMode = 'any' }, toFirstPage] = useShortUrlsQuery(); +): FC => ({ 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 = (
- +
+
+ +
+ +
diff --git a/src/short-urls/ShortUrlsList.tsx b/src/short-urls/ShortUrlsList.tsx index 60bfa759..a124368c 100644 --- a/src/short-urls/ShortUrlsList.tsx +++ b/src/short-urls/ShortUrlsList.tsx @@ -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" /> diff --git a/src/short-urls/helpers/ExportShortUrlsBtn.tsx b/src/short-urls/helpers/ExportShortUrlsBtn.tsx index 5a5f1233..35403d1d 100644 --- a/src/short-urls/helpers/ExportShortUrlsBtn.tsx +++ b/src/short-urls/helpers/ExportShortUrlsBtn.tsx @@ -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(); }; diff --git a/test/short-urls/ShortUrlsFilteringBar.test.tsx b/test/short-urls/ShortUrlsFilteringBar.test.tsx index d8f30942..ad366d98 100644 --- a/test/short-urls/ShortUrlsFilteringBar.test.tsx +++ b/test/short-urls/ShortUrlsFilteringBar.test.tsx @@ -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('', () => { selectedServer={selectedServer ?? Mock.all()} order={{}} handleOrderBy={handleOrderBy} + settings={Mock.of({ visits: {} })} /> , ); @@ -114,6 +116,24 @@ describe('', () => { 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({ 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) => {