From 4ebe23e89f71e7686de5f1159402a192879fc14c Mon Sep 17 00:00:00 2001 From: Alejandro Celaya Date: Sun, 19 Mar 2023 10:32:11 +0100 Subject: [PATCH] Add logic to dynamically exclude bots visits in tags table --- package.json | 3 ++- src/api/types/index.ts | 3 +++ src/tags/TagsList.tsx | 21 ++++++++++++++------- src/tags/TagsTableRow.tsx | 4 ++-- src/tags/data/TagsListChildrenProps.ts | 4 ++-- src/tags/data/index.ts | 5 ++++- src/tags/reducers/tagsList.ts | 8 ++++---- src/tags/services/provideServices.ts | 1 + test/tags/TagsList.test.tsx | 2 +- test/tags/TagsTable.test.tsx | 4 ++-- 10 files changed, 35 insertions(+), 20 deletions(-) diff --git a/package.json b/package.json index e9d4c8bd..66ebcd44 100644 --- a/package.json +++ b/package.json @@ -12,9 +12,10 @@ "lint:fix": "npm run lint:css:fix && npm run lint:js:fix", "lint:css:fix": "npm run lint:css -- --fix", "lint:js:fix": "npm run lint:js -- --fix", + "types": "tsc", "start": "vite serve --host=0.0.0.0", "preview": "vite preview --host=0.0.0.0", - "build": "tsc --noEmit && vite build && node scripts/replace-version.mjs", + "build": "npm run types && vite build && node scripts/replace-version.mjs", "build:dist": "npm run build && node scripts/create-dist-file.mjs", "test": "jest --env=jsdom --colors", "test:coverage": "npm run test -- --coverage --coverageReporters=text --coverageReporters=text-summary", diff --git a/src/api/types/index.ts b/src/api/types/index.ts index 59c4e05b..e97dddee 100644 --- a/src/api/types/index.ts +++ b/src/api/types/index.ts @@ -21,6 +21,9 @@ export interface ShlinkHealth { interface ShlinkTagsStats { tag: string; shortUrlsCount: number; + visitsSummary?: ShlinkVisitsSummary; // Optional only before Shlink 3.5.0 + + /** @deprecated */ visitsCount: number; } diff --git a/src/tags/TagsList.tsx b/src/tags/TagsList.tsx index a2e35501..13aad995 100644 --- a/src/tags/TagsList.tsx +++ b/src/tags/TagsList.tsx @@ -12,7 +12,7 @@ import { Message } from '../utils/Message'; import { OrderingDropdown } from '../utils/OrderingDropdown'; import { Result } from '../utils/Result'; import { SearchField } from '../utils/SearchField'; -import type { NormalizedTag } from './data'; +import type { SimplifiedTag } from './data'; import type { TagsOrder, TagsOrderableFields } from './data/TagsListChildrenProps'; import { TAGS_ORDERABLE_FIELDS } from './data/TagsListChildrenProps'; import type { TagsList as TagsListState } from './reducers/tagsList'; @@ -31,12 +31,19 @@ export const TagsList = (TagsTable: FC) => boundToMercureHub(( ) => { const [order, setOrder] = useState(settings.tags?.defaultOrdering ?? {}); const resolveSortedTags = pipe( - () => tagsList.filteredTags.map((tag): NormalizedTag => ({ - tag, - shortUrls: tagsList.stats[tag]?.shortUrlsCount ?? 0, - visits: tagsList.stats[tag]?.visitsCount ?? 0, - })), - (normalizedTags) => sortList(normalizedTags, order), + () => tagsList.filteredTags.map((tag): SimplifiedTag => { + const theTag = tagsList.stats[tag]; + const visits = ( + settings.visits?.excludeBots ? theTag?.visitsSummary?.nonBots : theTag?.visitsSummary?.total + ) ?? theTag?.visitsCount ?? 0; + + return { + tag, + visits, + shortUrls: theTag?.shortUrlsCount ?? 0, + }; + }), + (simplifiedTags) => sortList(simplifiedTags, order), ); useEffect(() => { diff --git a/src/tags/TagsTableRow.tsx b/src/tags/TagsTableRow.tsx index aa8ea5a5..d3257bae 100644 --- a/src/tags/TagsTableRow.tsx +++ b/src/tags/TagsTableRow.tsx @@ -9,11 +9,11 @@ import { DropdownBtnMenu } from '../utils/DropdownBtnMenu'; import { useToggle } from '../utils/helpers/hooks'; import { prettify } from '../utils/helpers/numbers'; import type { ColorGenerator } from '../utils/services/ColorGenerator'; -import type { NormalizedTag, TagModalProps } from './data'; +import type { SimplifiedTag, TagModalProps } from './data'; import { TagBullet } from './helpers/TagBullet'; export interface TagsTableRowProps { - tag: NormalizedTag; + tag: SimplifiedTag; selectedServer: SelectedServer; } diff --git a/src/tags/data/TagsListChildrenProps.ts b/src/tags/data/TagsListChildrenProps.ts index a20720bc..3e2843eb 100644 --- a/src/tags/data/TagsListChildrenProps.ts +++ b/src/tags/data/TagsListChildrenProps.ts @@ -1,6 +1,6 @@ import type { SelectedServer } from '../../servers/data'; import type { Order } from '../../utils/helpers/ordering'; -import type { NormalizedTag } from './index'; +import type { SimplifiedTag } from './index'; export const TAGS_ORDERABLE_FIELDS = { tag: 'Tag', @@ -13,6 +13,6 @@ export type TagsOrderableFields = keyof typeof TAGS_ORDERABLE_FIELDS; export type TagsOrder = Order; export interface TagsListChildrenProps { - sortedTags: NormalizedTag[]; + sortedTags: SimplifiedTag[]; selectedServer: SelectedServer; } diff --git a/src/tags/data/index.ts b/src/tags/data/index.ts index 8210a98b..acc59a5b 100644 --- a/src/tags/data/index.ts +++ b/src/tags/data/index.ts @@ -1,6 +1,9 @@ +import type { ShlinkVisitsSummary } from '../../api/types'; + export interface TagStats { shortUrlsCount: number; visitsCount: number; + visitsSummary?: ShlinkVisitsSummary; // Optional only before Shlink 3.5.0 } export interface TagModalProps { @@ -9,7 +12,7 @@ export interface TagModalProps { toggle: () => void; } -export interface NormalizedTag { +export interface SimplifiedTag { tag: string; shortUrls: number; visits: number; diff --git a/src/tags/reducers/tagsList.ts b/src/tags/reducers/tagsList.ts index e96c8a23..9582659f 100644 --- a/src/tags/reducers/tagsList.ts +++ b/src/tags/reducers/tagsList.ts @@ -50,6 +50,7 @@ const increaseVisitsForTags = (tags: TagIncrease[], stats: TagsStatsMap) => tags const tagStats = theStats[tag]; + // TODO take into consideration bots, nonBots and total return { ...theStats, [tag]: { @@ -78,12 +79,11 @@ export const listTags = (buildShlinkApiClient: ShlinkApiClientBuilder, force = t } const { listTags: shlinkListTags, tagsStats } = buildShlinkApiClient(getState); - const { tags, stats = [] }: ShlinkTags = await ( + const { tags, stats }: ShlinkTags = await ( supportedFeatures.tagsStats(selectedServer) ? tagsStats() : shlinkListTags() ); - const processedStats = stats.reduce((acc, { tag, shortUrlsCount, visitsCount }) => { - acc[tag] = { shortUrlsCount, visitsCount }; - + const processedStats = stats.reduce((acc, { tag, ...rest }) => { + acc[tag] = rest; return acc; }, {}); diff --git a/src/tags/services/provideServices.ts b/src/tags/services/provideServices.ts index 8fbbe9cb..50d32e49 100644 --- a/src/tags/services/provideServices.ts +++ b/src/tags/services/provideServices.ts @@ -24,6 +24,7 @@ export const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.decorator('EditTagModal', connect(['tagEdit'], ['editTag', 'tagEdited'])); bottle.serviceFactory('TagsTableRow', TagsTableRow, 'DeleteTagConfirmModal', 'EditTagModal', 'ColorGenerator'); + bottle.decorator('TagsTableRow', connect(['settings'])); bottle.serviceFactory('TagsTable', TagsTable, 'TagsTableRow'); diff --git a/test/tags/TagsList.test.tsx b/test/tags/TagsList.test.tsx index b20a8115..64dd7afa 100644 --- a/test/tags/TagsList.test.tsx +++ b/test/tags/TagsList.test.tsx @@ -10,7 +10,7 @@ import { renderWithEvents } from '../__helpers__/setUpTest'; describe('', () => { const filterTags = jest.fn(); - const TagsListComp = createTagsList(() => <>TagsTable); + const TagsListComp = createTagsList(({ sortedTags }) => <>TagsTable ({sortedTags.map((t) => t.visits).join(',')})); const setUp = (tagsList: Partial) => renderWithEvents( ()} diff --git a/test/tags/TagsTable.test.tsx b/test/tags/TagsTable.test.tsx index 4c212cf0..da187a6a 100644 --- a/test/tags/TagsTable.test.tsx +++ b/test/tags/TagsTable.test.tsx @@ -2,7 +2,7 @@ import { screen } from '@testing-library/react'; import { useLocation } from 'react-router-dom'; import { Mock } from 'ts-mockery'; import type { SelectedServer } from '../../src/servers/data'; -import type { NormalizedTag } from '../../src/tags/data'; +import type { SimplifiedTag } from '../../src/tags/data'; import { TagsTable as createTagsTable } from '../../src/tags/TagsTable'; import { rangeOf } from '../../src/utils/utils'; import { renderWithEvents } from '../__helpers__/setUpTest'; @@ -17,7 +17,7 @@ describe('', () => { (useLocation as any).mockReturnValue({ search }); return renderWithEvents( Mock.of({ tag }))} + sortedTags={sortedTags.map((tag) => Mock.of({ tag }))} selectedServer={Mock.all()} currentOrder={{}} orderByColumn={() => orderByColumn}