mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Merge pull request #828 from acelaya-forks/feature/short-url-export
Feature/short url export
This commit is contained in:
commit
86349f1ad3
6 changed files with 57 additions and 11 deletions
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -4,6 +4,23 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).
|
||||||
|
|
||||||
|
## [Unreleased]
|
||||||
|
### Added
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Deprecated
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
* *Nothing*
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
* [#826](https://github.com/shlinkio/shlink-web-client/issues/826) Fix generated short URLs CSV so that it can be used to import on Shlink.
|
||||||
|
|
||||||
|
|
||||||
## [3.10.0] - 2023-03-19
|
## [3.10.0] - 2023-03-19
|
||||||
### Added
|
### Added
|
||||||
* [#807](https://github.com/shlinkio/shlink-web-client/issues/807) Add support for device-specific long-URLs when creating or editing short URLs.
|
* [#807](https://github.com/shlinkio/shlink-web-client/issues/807) Add support for device-specific long-URLs when creating or editing short URLs.
|
||||||
|
|
|
@ -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);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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} />;
|
||||||
};
|
};
|
||||||
|
|
|
@ -53,6 +53,7 @@ describe('ReportExporter', () => {
|
||||||
createdAt: '',
|
createdAt: '',
|
||||||
longUrl: '',
|
longUrl: '',
|
||||||
tags: '',
|
tags: '',
|
||||||
|
shortCode: '',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
})]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue