From 934bf495a08b45fe6d33d2ebb59246801395849a Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sat, 18 Mar 2023 10:55:07 +0100 Subject: [PATCH] Extend overview to exclude/include bot visits based on config --- src/servers/Overview.tsx | 26 ++++++++---- src/servers/helpers/VisitsHighlightCard.tsx | 26 ++++++++++++ src/servers/services/provideServices.ts | 2 +- test/servers/Overview.test.tsx | 40 +++++++++++++++---- .../helpers/ShortUrlStatus.test.tsx | 9 +++-- 5 files changed, 84 insertions(+), 19 deletions(-) create mode 100644 src/servers/helpers/VisitsHighlightCard.tsx diff --git a/src/servers/Overview.tsx b/src/servers/Overview.tsx index 58f55668..83b7d7c3 100644 --- a/src/servers/Overview.tsx +++ b/src/servers/Overview.tsx @@ -5,6 +5,7 @@ import { Card, CardBody, CardHeader, Row } from 'reactstrap'; import type { ShlinkShortUrlsListParams } from '../api/types'; import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub'; import { Topics } from '../mercure/helpers/Topics'; +import type { Settings } from '../settings/reducers/settings'; import type { CreateShortUrlProps } from '../short-urls/CreateShortUrl'; import type { ShortUrlsList as ShortUrlsListState } from '../short-urls/reducers/shortUrlsList'; import { ITEMS_IN_OVERVIEW_PAGE } from '../short-urls/reducers/shortUrlsList'; @@ -16,6 +17,7 @@ import type { VisitsOverview } from '../visits/reducers/visitsOverview'; import type { SelectedServer } from './data'; import { getServerId } from './data'; import { HighlightCard } from './helpers/HighlightCard'; +import { VisitsHighlightCard } from './helpers/VisitsHighlightCard'; interface OverviewConnectProps { shortUrlsList: ShortUrlsListState; @@ -25,6 +27,7 @@ interface OverviewConnectProps { selectedServer: SelectedServer; visitsOverview: VisitsOverview; loadVisitsOverview: Function; + settings: Settings; } export const Overview = ( @@ -38,10 +41,11 @@ export const Overview = ( selectedServer, loadVisitsOverview, visitsOverview, + settings: { visits }, }: OverviewConnectProps) => { const { loading, shortUrls } = shortUrlsList; const { loading: loadingTags } = tagsList; - const { loading: loadingVisits, visitsCount, orphanVisitsCount } = visitsOverview; + const { loading: loadingVisits, nonOrphanVisits, orphanVisits } = visitsOverview; const serverId = getServerId(selectedServer); const linkToNonOrphanVisits = useFeature('nonOrphanVisits', selectedServer); const navigate = useNavigate(); @@ -56,14 +60,22 @@ export const Overview = ( <>
- - {loadingVisits ? 'Loading...' : prettify(visitsCount)} - +
- - {loadingVisits ? 'Loading...' : prettify(orphanVisitsCount)} - +
diff --git a/src/servers/helpers/VisitsHighlightCard.tsx b/src/servers/helpers/VisitsHighlightCard.tsx new file mode 100644 index 00000000..bfe9b791 --- /dev/null +++ b/src/servers/helpers/VisitsHighlightCard.tsx @@ -0,0 +1,26 @@ +import type { FC } from 'react'; +import { prettify } from '../../utils/helpers/numbers'; +import type { PartialVisitsSummary } from '../../visits/reducers/visitsOverview'; +import type { HighlightCardProps } from './HighlightCard'; +import { HighlightCard } from './HighlightCard'; + +type VisitsHighlightCardProps = Omit & { + loading: boolean; + excludeBots: boolean; + visitsSummary: PartialVisitsSummary; +}; + +export const VisitsHighlightCard: FC = ({ loading, excludeBots, visitsSummary, ...rest }) => ( + {excludeBots ? 'Plus' : 'Including'} {prettify(visitsSummary.bots)} potential bot visits + : undefined + } + {...rest} + > + {loading ? 'Loading...' : prettify( + excludeBots && visitsSummary.nonBots ? visitsSummary.nonBots : visitsSummary.total, + )} + +); diff --git a/src/servers/services/provideServices.ts b/src/servers/services/provideServices.ts index a7831580..fb8d4298 100644 --- a/src/servers/services/provideServices.ts +++ b/src/servers/services/provideServices.ts @@ -65,7 +65,7 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('Overview', Overview, 'ShortUrlsTable', 'CreateShortUrl'); bottle.decorator('Overview', connect( - ['shortUrlsList', 'tagsList', 'selectedServer', 'mercureInfo', 'visitsOverview'], + ['shortUrlsList', 'tagsList', 'selectedServer', 'mercureInfo', 'visitsOverview', 'settings'], ['listShortUrls', 'listTags', 'createNewVisits', 'loadMercureInfo', 'loadVisitsOverview'], )); diff --git a/test/servers/Overview.test.tsx b/test/servers/Overview.test.tsx index 4b20e3a1..0b4c6b6d 100644 --- a/test/servers/Overview.test.tsx +++ b/test/servers/Overview.test.tsx @@ -1,13 +1,15 @@ -import { render, screen } from '@testing-library/react'; +import { screen, waitFor } from '@testing-library/react'; import { MemoryRouter } from 'react-router-dom'; import { Mock } from 'ts-mockery'; import type { MercureInfo } from '../../src/mercure/reducers/mercureInfo'; import type { ReachableServer } from '../../src/servers/data'; import { Overview as overviewCreator } from '../../src/servers/Overview'; +import type { Settings } from '../../src/settings/reducers/settings'; import type { ShortUrlsList as ShortUrlsListState } from '../../src/short-urls/reducers/shortUrlsList'; import type { TagsList } from '../../src/tags/reducers/tagsList'; import { prettify } from '../../src/utils/helpers/numbers'; import type { VisitsOverview } from '../../src/visits/reducers/visitsOverview'; +import { renderWithEvents } from '../__helpers__/setUpTest'; describe('', () => { const ShortUrlsTable = () => <>ShortUrlsTable; @@ -20,7 +22,7 @@ describe('', () => { pagination: { totalItems: 83710 }, }; const serverId = '123'; - const setUp = (loading = false) => render( + const setUp = (loading = false, excludeBots = false) => renderWithEvents( ', () => { loadVisitsOverview={loadVisitsOverview} shortUrlsList={Mock.of({ loading, shortUrls })} tagsList={Mock.of({ loading, tags: ['foo', 'bar', 'baz'] })} - visitsOverview={Mock.of({ loading, visitsCount: 3456, orphanVisitsCount: 28 })} + visitsOverview={Mock.of({ + loading, + nonOrphanVisits: { total: 3456, bots: 1000, nonBots: 2456 }, + orphanVisits: { total: 28, bots: 15, nonBots: 13 }, + })} selectedServer={Mock.of({ id: serverId })} createNewVisits={jest.fn()} loadMercureInfo={jest.fn()} mercureInfo={Mock.all()} + settings={Mock.of({ visits: { excludeBots } })} /> , ); @@ -42,16 +49,19 @@ describe('', () => { expect(screen.getAllByText('Loading...')).toHaveLength(4); }); - it('displays amounts in cards after finishing loading', () => { - setUp(); + it.each([ + [false, 3456, 28], + [true, 2456, 13], + ])('displays amounts in cards after finishing loading', (excludeBots, expectedVisits, expectedOrphanVisits) => { + setUp(false, excludeBots); const headingElements = screen.getAllByRole('heading'); expect(screen.queryByText('Loading...')).not.toBeInTheDocument(); expect(headingElements[0]).toHaveTextContent('Visits'); - expect(headingElements[1]).toHaveTextContent(prettify(3456)); + expect(headingElements[1]).toHaveTextContent(prettify(expectedVisits)); expect(headingElements[2]).toHaveTextContent('Orphan visits'); - expect(headingElements[3]).toHaveTextContent(prettify(28)); + expect(headingElements[3]).toHaveTextContent(prettify(expectedOrphanVisits)); expect(headingElements[4]).toHaveTextContent('Short URLs'); expect(headingElements[5]).toHaveTextContent(prettify(83710)); expect(headingElements[6]).toHaveTextContent('Tags'); @@ -77,4 +87,20 @@ describe('', () => { expect(links[3]).toHaveAttribute('href', `/server/${serverId}/create-short-url`); expect(links[4]).toHaveAttribute('href', `/server/${serverId}/list-short-urls/1`); }); + + it.each([ + [true], + [false], + ])('displays amounts of bots when hovering visits cards', async (excludeBots) => { + const { user } = setUp(false, excludeBots); + const expectTooltipToBeInTheDocument = async (tooltip: string) => waitFor( + () => expect(screen.getByText(/potential bot visits$/)).toHaveTextContent(tooltip), + ); + + await user.hover(screen.getByText(/^Visits/)); + await expectTooltipToBeInTheDocument(`${excludeBots ? 'Plus' : 'Including'} 1,000 potential bot visits`); + + await user.hover(screen.getByText(/^Orphan visits/)); + await expectTooltipToBeInTheDocument(`${excludeBots ? 'Plus' : 'Including'} 15 potential bot visits`); + }); }); diff --git a/test/short-urls/helpers/ShortUrlStatus.test.tsx b/test/short-urls/helpers/ShortUrlStatus.test.tsx index 4f373503..1c5ea483 100644 --- a/test/short-urls/helpers/ShortUrlStatus.test.tsx +++ b/test/short-urls/helpers/ShortUrlStatus.test.tsx @@ -1,7 +1,8 @@ import { render, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { Mock } from 'ts-mockery'; -import type { ShortUrl, ShortUrlMeta, ShortUrlVisitsSummary } from '../../../src/short-urls/data'; +import type { ShlinkVisitsSummary } from '../../../src/api/types'; +import type { ShortUrl, ShortUrlMeta } from '../../../src/short-urls/data'; import { ShortUrlStatus } from '../../../src/short-urls/helpers/ShortUrlStatus'; describe('', () => { @@ -23,12 +24,12 @@ describe('', () => { ], [ Mock.of({ maxVisits: 10 }), - Mock.of({ total: 10 }), + Mock.of({ total: 10 }), 'This short URL cannot be currently visited because it has reached the maximum amount of 10 visits.', ], [ Mock.of({ maxVisits: 1 }), - Mock.of({ total: 1 }), + Mock.of({ total: 1 }), 'This short URL cannot be currently visited because it has reached the maximum amount of 1 visit.', ], [{}, {}, 'This short URL can be visited normally.'], @@ -36,7 +37,7 @@ describe('', () => { [Mock.of({ validSince: '2020-01-01T10:30:15' }), {}, 'This short URL can be visited normally.'], [ Mock.of({ maxVisits: 10 }), - Mock.of({ total: 1 }), + Mock.of({ total: 1 }), 'This short URL can be visited normally.', ], ])('shows expected tooltip', async (meta, visitsSummary, expectedTooltip) => {