Refactor short URL export so that it is compatible with what Shlink expects

This commit is contained in:
Alejandro Celaya 2023-04-21 09:36:51 +02:00
parent 6fbe6c673b
commit 992b22fd24
4 changed files with 39 additions and 11 deletions

View file

@ -24,7 +24,6 @@ export class ReportExporter {
private readonly exportCsv = (filename: string, rows: object[]) => { private readonly exportCsv = (filename: string, rows: object[]) => {
const csv = this.jsonToCsv(rows); const csv = this.jsonToCsv(rows);
saveCsv(this.window, csv, filename); saveCsv(this.window, csv, filename);
}; };
} }

View file

@ -79,6 +79,8 @@ export interface ExportableShortUrl {
createdAt: string; createdAt: string;
title: string; title: string;
shortUrl: string; shortUrl: string;
domain?: string;
shortCode: string;
longUrl: string; longUrl: string;
tags: string; tags: string;
visits: number; visits: number;

View file

@ -1,4 +1,5 @@
import type { FC } from 'react'; import type { FC } from 'react';
import { useCallback } from 'react';
import type { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder'; import type { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
import type { ReportExporter } from '../../common/services/ReportExporter'; import type { ReportExporter } from '../../common/services/ReportExporter';
import type { SelectedServer } from '../../servers/data'; import type { SelectedServer } from '../../servers/data';
@ -24,7 +25,7 @@ export const ExportShortUrlsBtn = (
): FC<ExportShortUrlsBtnConnectProps> => ({ amount = 0, selectedServer }) => { ): FC<ExportShortUrlsBtnConnectProps> => ({ amount = 0, selectedServer }) => {
const [{ tags, search, startDate, endDate, orderBy, tagsMode }] = useShortUrlsQuery(); const [{ tags, search, startDate, endDate, orderBy, tagsMode }] = useShortUrlsQuery();
const [loading,, startLoading, stopLoading] = useToggle(); const [loading,, startLoading, stopLoading] = useToggle();
const exportAllUrls = async () => { const exportAllUrls = useCallback(async () => {
if (!isServerWithId(selectedServer)) { if (!isServerWithId(selectedServer)) {
return; return;
} }
@ -47,16 +48,23 @@ export const ExportShortUrlsBtn = (
startLoading(); startLoading();
const shortUrls = await loadAllUrls(); const shortUrls = await loadAllUrls();
exportShortUrls(shortUrls.map((shortUrl) => ({ exportShortUrls(shortUrls.map((shortUrl) => {
createdAt: shortUrl.dateCreated, const { hostname: domain, pathname } = new URL(shortUrl.shortUrl);
shortUrl: shortUrl.shortUrl, const shortCode = pathname.substring(1); // Remove trailing slash
longUrl: shortUrl.longUrl,
title: shortUrl.title ?? '', return {
tags: shortUrl.tags.join(','), createdAt: shortUrl.dateCreated,
visits: shortUrl?.visitsSummary?.total ?? shortUrl.visitsCount, domain,
}))); shortCode,
shortUrl: shortUrl.shortUrl,
longUrl: shortUrl.longUrl,
title: shortUrl.title ?? '',
tags: shortUrl.tags.join('|'),
visits: shortUrl?.visitsSummary?.total ?? shortUrl.visitsCount,
};
}));
stopLoading(); stopLoading();
}; }, [selectedServer]);
return <ExportBtn loading={loading} className="btn-md-block" amount={amount} onClick={exportAllUrls} />; return <ExportBtn loading={loading} className="btn-md-block" amount={amount} onClick={exportAllUrls} />;
}; };

View file

@ -3,6 +3,7 @@ import { fromPartial } from '@total-typescript/shoehorn';
import { MemoryRouter } from 'react-router-dom'; import { MemoryRouter } from 'react-router-dom';
import type { ReportExporter } from '../../../src/common/services/ReportExporter'; import type { ReportExporter } from '../../../src/common/services/ReportExporter';
import type { NotFoundServer, SelectedServer } from '../../../src/servers/data'; import type { NotFoundServer, SelectedServer } from '../../../src/servers/data';
import type { ShortUrl } from '../../../src/short-urls/data';
import { ExportShortUrlsBtn as createExportShortUrlsBtn } from '../../../src/short-urls/helpers/ExportShortUrlsBtn'; import { ExportShortUrlsBtn as createExportShortUrlsBtn } from '../../../src/short-urls/helpers/ExportShortUrlsBtn';
import { renderWithEvents } from '../../__helpers__/setUpTest'; import { renderWithEvents } from '../../__helpers__/setUpTest';
@ -56,4 +57,22 @@ describe('<ExportShortUrlsBtn />', () => {
expect(listShortUrls).toHaveBeenCalledTimes(expectedPageLoads); expect(listShortUrls).toHaveBeenCalledTimes(expectedPageLoads);
expect(exportShortUrls).toHaveBeenCalled(); expect(exportShortUrls).toHaveBeenCalled();
}); });
it('maps short URLs for exporting', async () => {
listShortUrls.mockResolvedValue({
data: [fromPartial<ShortUrl>({
shortUrl: 'https://s.test/short-code',
tags: [],
})],
});
const { user } = setUp(undefined, fromPartial({ id: '123' }));
await user.click(screen.getByRole('button'));
expect(exportShortUrls).toHaveBeenCalledWith([expect.objectContaining({
shortUrl: 'https://s.test/short-code',
domain: 's.test',
shortCode: 'short-code',
})]);
});
}); });