mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2025-01-03 14:57:22 +03:00
Merge pull request #817 from acelaya-forks/feature/tags-stats
Feature/tags stats
This commit is contained in:
commit
8fa61a6301
6 changed files with 46 additions and 6 deletions
|
@ -10,9 +10,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|||
* [#808](https://github.com/shlinkio/shlink-web-client/issues/808) Respect settings on excluding bots in the overview section, for visits cards.
|
||||
|
||||
### Changed
|
||||
* Update to Vite 4.1
|
||||
* Update to coding standard v2.1.0
|
||||
* [#798](https://github.com/shlinkio/shlink-web-client/issues/798) Remove stryker and mutation testing.
|
||||
* [#800](https://github.com/shlinkio/shlink-web-client/issues/800) Use `/tags/stats` endpoint to load tags stats, when the server supports it.
|
||||
* Update to Vite 4.2
|
||||
* Update to TypeScript 5
|
||||
* Update to coding standard v2.1.0
|
||||
* Decouple tests from RTK internals.
|
||||
|
||||
### Deprecated
|
||||
* *Nothing*
|
||||
|
|
|
@ -16,6 +16,7 @@ import type {
|
|||
ShlinkShortUrlsResponse,
|
||||
ShlinkTags,
|
||||
ShlinkTagsResponse,
|
||||
ShlinkTagsStatsResponse,
|
||||
ShlinkVisits,
|
||||
ShlinkVisitsOverview,
|
||||
ShlinkVisitsParams,
|
||||
|
@ -90,6 +91,11 @@ export class ShlinkApiClient {
|
|||
.then(({ tags }) => tags)
|
||||
.then(({ data, stats }) => ({ tags: data, stats }));
|
||||
|
||||
public readonly tagsStats = async (): Promise<ShlinkTags> =>
|
||||
this.performRequest<{ tags: ShlinkTagsStatsResponse }>('/tags/stats', 'GET')
|
||||
.then(({ tags }) => tags)
|
||||
.then(({ data }) => ({ tags: data.map(({ tag }) => tag), stats: data }));
|
||||
|
||||
public readonly deleteTags = async (tags: string[]): Promise<{ tags: string[] }> =>
|
||||
this.performEmptyRequest('/tags', 'DELETE', { tags }).then(() => ({ tags }));
|
||||
|
||||
|
|
|
@ -31,9 +31,14 @@ export interface ShlinkTags {
|
|||
|
||||
export interface ShlinkTagsResponse {
|
||||
data: string[];
|
||||
/** @deprecated Present only when withStats=true is provided, which is deprecated */
|
||||
stats: ShlinkTagsStats[];
|
||||
}
|
||||
|
||||
export interface ShlinkTagsStatsResponse {
|
||||
data: ShlinkTagsStats[];
|
||||
}
|
||||
|
||||
export interface ShlinkPaginator {
|
||||
currentPage: number;
|
||||
pagesCount: number;
|
||||
|
|
|
@ -5,6 +5,7 @@ import type { ShlinkTags } from '../../api/types';
|
|||
import type { ProblemDetailsError } from '../../api/types/errors';
|
||||
import { parseApiError } from '../../api/utils';
|
||||
import type { createShortUrl } from '../../short-urls/reducers/shortUrlCreation';
|
||||
import { supportedFeatures } from '../../utils/helpers/features';
|
||||
import { createAsyncThunk } from '../../utils/helpers/redux';
|
||||
import { createNewVisits } from '../../visits/reducers/visitCreation';
|
||||
import type { CreateVisit, Stats } from '../../visits/types';
|
||||
|
@ -70,14 +71,16 @@ const calculateVisitsPerTag = (createdVisits: CreateVisit[]): TagIncrease[] => O
|
|||
export const listTags = (buildShlinkApiClient: ShlinkApiClientBuilder, force = true) => createAsyncThunk(
|
||||
`${REDUCER_PREFIX}/listTags`,
|
||||
async (_: void, { getState }): Promise<ListTags> => {
|
||||
const { tagsList } = getState();
|
||||
const { tagsList, selectedServer } = getState();
|
||||
|
||||
if (!force && !isEmpty(tagsList.tags)) {
|
||||
return tagsList;
|
||||
}
|
||||
|
||||
const { listTags: shlinkListTags } = buildShlinkApiClient(getState);
|
||||
const { tags, stats = [] }: ShlinkTags = await shlinkListTags();
|
||||
const { listTags: shlinkListTags, tagsStats } = buildShlinkApiClient(getState);
|
||||
const { tags, stats = [] }: ShlinkTags = await (
|
||||
supportedFeatures.tagsStats(selectedServer) ? tagsStats() : shlinkListTags()
|
||||
);
|
||||
const processedStats = stats.reduce<TagsStatsMap>((acc, { tag, shortUrlsCount, visitsCount }) => {
|
||||
acc[tag] = { shortUrlsCount, visitsCount };
|
||||
|
||||
|
|
|
@ -14,11 +14,12 @@ export const supportedFeatures = {
|
|||
defaultDomainRedirectsEdition: matchesMinVersion('2.10.0'),
|
||||
nonOrphanVisits: matchesMinVersion('3.0.0'),
|
||||
allTagsFiltering: matchesMinVersion('3.0.0'),
|
||||
tagsStats: matchesMinVersion('3.0.0'),
|
||||
domainVisits: matchesMinVersion('3.1.0'),
|
||||
excludeBotsOnShortUrls: matchesMinVersion('3.4.0'),
|
||||
filterDisabledUrls: matchesMinVersion('3.4.0'),
|
||||
deviceLongUrls: matchesMinVersion('3.5.0'),
|
||||
} as const;
|
||||
} as const satisfies Record<string, ReturnType<typeof matchesMinVersion>>;
|
||||
|
||||
Object.freeze(supportedFeatures);
|
||||
|
||||
|
|
|
@ -212,6 +212,28 @@ describe('ShlinkApiClient', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('tagsStats', () => {
|
||||
it('can use /tags/stats endpoint', async () => {
|
||||
const expectedTags = ['foo', 'bar'];
|
||||
const expectedStats = expectedTags.map((tag) => ({ tag, shortUrlsCount: 10, visitsCount: 10 }));
|
||||
|
||||
fetchJson.mockResolvedValue({
|
||||
tags: {
|
||||
data: expectedStats,
|
||||
},
|
||||
});
|
||||
const { tagsStats } = buildApiClient();
|
||||
|
||||
const result = await tagsStats();
|
||||
|
||||
expect({ tags: expectedTags, stats: expectedStats }).toEqual(result);
|
||||
expect(fetchJson).toHaveBeenCalledWith(
|
||||
expect.stringContaining('/tags/stats'),
|
||||
expect.objectContaining({ method: 'GET' }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteTags', () => {
|
||||
it('properly deletes provided tags', async () => {
|
||||
const tags = ['foo', 'bar'];
|
||||
|
|
Loading…
Reference in a new issue