Merge pull request #561 from acelaya-forks/feature/overview-list

Fixed short URLs list in overview page
This commit is contained in:
Alejandro Celaya 2022-01-08 10:55:02 +01:00 committed by GitHub
commit 83fbdbb135
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 45 additions and 15 deletions

View file

@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
### Fixed ### Fixed
* [#555](https://github.com/shlinkio/shlink-web-client/issues/555) Fixed vertical alignment in welcome screen logo. * [#555](https://github.com/shlinkio/shlink-web-client/issues/555) Fixed vertical alignment in welcome screen logo.
* [#554](https://github.com/shlinkio/shlink-web-client/issues/554) Fixed behavior in overview page, where items in the list of short URLs were stripped out when creating new ones, even if the amount of short URLs was still not yet big enough.
## [3.5.0] - 2022-01-01 ## [3.5.0] - 2022-01-01

View file

@ -1,7 +1,7 @@
import { FC, useEffect } from 'react'; import { FC, useEffect } from 'react';
import { Card, CardBody, CardHeader, CardText, CardTitle, Row } from 'reactstrap'; import { Card, CardBody, CardHeader, CardText, CardTitle, Row } from 'reactstrap';
import { Link, useHistory } from 'react-router-dom'; import { Link, useHistory } from 'react-router-dom';
import { ShortUrlsList as ShortUrlsListState } from '../short-urls/reducers/shortUrlsList'; import { ITEMS_IN_OVERVIEW_PAGE, ShortUrlsList as ShortUrlsListState } from '../short-urls/reducers/shortUrlsList';
import { prettify } from '../utils/helpers/numbers'; import { prettify } from '../utils/helpers/numbers';
import { TagsList } from '../tags/reducers/tagsList'; import { TagsList } from '../tags/reducers/tagsList';
import { ShortUrlsTableProps } from '../short-urls/ShortUrlsTable'; import { ShortUrlsTableProps } from '../short-urls/ShortUrlsTable';
@ -44,7 +44,7 @@ export const Overview = (
const history = useHistory(); const history = useHistory();
useEffect(() => { useEffect(() => {
listShortUrls({ itemsPerPage: 5, orderBy: { field: 'dateCreated', dir: 'DESC' } }); listShortUrls({ itemsPerPage: ITEMS_IN_OVERVIEW_PAGE, orderBy: { field: 'dateCreated', dir: 'DESC' } });
listTags(); listTags();
loadVisitsOverview(); loadVisitsOverview();
}, []); }, []);

View file

@ -1,4 +1,4 @@
import { assoc, assocPath, init, last, pipe, reject } from 'ramda'; import { assoc, assocPath, last, pipe, reject } from 'ramda';
import { Action, Dispatch } from 'redux'; import { Action, Dispatch } from 'redux';
import { shortUrlMatches } from '../helpers'; import { shortUrlMatches } from '../helpers';
import { CREATE_VISITS, CreateVisitsAction } from '../../visits/reducers/visitCreation'; import { CREATE_VISITS, CreateVisitsAction } from '../../visits/reducers/visitCreation';
@ -16,6 +16,8 @@ export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR
export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS'; export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS';
/* eslint-enable padding-line-between-statements */ /* eslint-enable padding-line-between-statements */
export const ITEMS_IN_OVERVIEW_PAGE = 5;
export interface ShortUrlsList { export interface ShortUrlsList {
shortUrls?: ShlinkShortUrlsResponse; shortUrls?: ShlinkShortUrlsResponse;
loading: boolean; loading: boolean;
@ -75,10 +77,11 @@ export default buildReducer<ShortUrlsList, ListShortUrlsCombinedAction>({
), ),
[CREATE_SHORT_URL]: pipe( [CREATE_SHORT_URL]: pipe(
// The only place where the list and the creation form coexist is the overview page. // The only place where the list and the creation form coexist is the overview page.
// There we can assume we are displaying page 1, and therefore, we can safely prepend the new short URL and remove the last one. // There we can assume we are displaying page 1, and therefore, we can safely prepend the new short URL.
// We can also remove the items above the amount that is displayed there.
(state: ShortUrlsList, { result }: CreateShortUrlAction) => !state.shortUrls ? state : assocPath( (state: ShortUrlsList, { result }: CreateShortUrlAction) => !state.shortUrls ? state : assocPath(
[ 'shortUrls', 'data' ], [ 'shortUrls', 'data' ],
[ result, ...init(state.shortUrls.data) ], [ result, ...state.shortUrls.data.slice(0, ITEMS_IN_OVERVIEW_PAGE - 1) ],
state, state,
), ),
(state: ShortUrlsList) => !state.shortUrls ? state : assocPath( (state: ShortUrlsList) => !state.shortUrls ? state : assocPath(

View file

@ -14,6 +14,8 @@ import { CREATE_SHORT_URL } from '../../../src/short-urls/reducers/shortUrlCreat
import { SHORT_URL_EDITED } from '../../../src/short-urls/reducers/shortUrlEdition'; import { SHORT_URL_EDITED } from '../../../src/short-urls/reducers/shortUrlEdition';
describe('shortUrlsListReducer', () => { describe('shortUrlsListReducer', () => {
const shortCode = 'abc123';
describe('reducer', () => { describe('reducer', () => {
it('returns loading on LIST_SHORT_URLS_START', () => it('returns loading on LIST_SHORT_URLS_START', () =>
expect(reducer(undefined, { type: LIST_SHORT_URLS_START } as any)).toEqual({ expect(reducer(undefined, { type: LIST_SHORT_URLS_START } as any)).toEqual({
@ -35,7 +37,6 @@ describe('shortUrlsListReducer', () => {
})); }));
it('removes matching URL and reduces total on SHORT_URL_DELETED', () => { it('removes matching URL and reduces total on SHORT_URL_DELETED', () => {
const shortCode = 'abc123';
const state = { const state = {
shortUrls: Mock.of<ShlinkShortUrlsResponse>({ shortUrls: Mock.of<ShlinkShortUrlsResponse>({
data: [ data: [
@ -72,7 +73,6 @@ describe('shortUrlsListReducer', () => {
[[{}], 10 ], [[{}], 10 ],
[[], 10 ], [[], 10 ],
])('updates visits count on CREATE_VISITS', (createdVisits, expectedCount) => { ])('updates visits count on CREATE_VISITS', (createdVisits, expectedCount) => {
const shortCode = 'abc123';
const state = { const state = {
shortUrls: Mock.of<ShlinkShortUrlsResponse>({ shortUrls: Mock.of<ShlinkShortUrlsResponse>({
data: [ data: [
@ -98,16 +98,42 @@ describe('shortUrlsListReducer', () => {
}); });
}); });
it('prepends new short URL and increases total on CREATE_SHORT_URL', () => { it.each([
const newShortUrl = Mock.of<ShortUrl>({ shortCode: 'newOne' }); [
const shortCode = 'abc123'; [
const state = {
shortUrls: Mock.of<ShlinkShortUrlsResponse>({
data: [
Mock.of<ShortUrl>({ shortCode }), Mock.of<ShortUrl>({ shortCode }),
Mock.of<ShortUrl>({ shortCode, domain: 'example.com' }), Mock.of<ShortUrl>({ shortCode, domain: 'example.com' }),
Mock.of<ShortUrl>({ shortCode: 'foo' }), Mock.of<ShortUrl>({ shortCode: 'foo' }),
], ],
[{ shortCode: 'newOne' }, { shortCode }, { shortCode, domain: 'example.com' }, { shortCode: 'foo' }],
],
[
[
Mock.of<ShortUrl>({ shortCode }),
Mock.of<ShortUrl>({ shortCode: 'code' }),
Mock.of<ShortUrl>({ shortCode: 'foo' }),
Mock.of<ShortUrl>({ shortCode: 'bar' }),
Mock.of<ShortUrl>({ shortCode: 'baz' }),
],
[{ shortCode: 'newOne' }, { shortCode }, { shortCode: 'code' }, { shortCode: 'foo' }, { shortCode: 'bar' }],
],
[
[
Mock.of<ShortUrl>({ shortCode }),
Mock.of<ShortUrl>({ shortCode: 'code' }),
Mock.of<ShortUrl>({ shortCode: 'foo' }),
Mock.of<ShortUrl>({ shortCode: 'bar' }),
Mock.of<ShortUrl>({ shortCode: 'baz1' }),
Mock.of<ShortUrl>({ shortCode: 'baz2' }),
Mock.of<ShortUrl>({ shortCode: 'baz3' }),
],
[{ shortCode: 'newOne' }, { shortCode }, { shortCode: 'code' }, { shortCode: 'foo' }, { shortCode: 'bar' }],
],
])('prepends new short URL and increases total on CREATE_SHORT_URL', (data, expectedData) => {
const newShortUrl = Mock.of<ShortUrl>({ shortCode: 'newOne' });
const state = {
shortUrls: Mock.of<ShlinkShortUrlsResponse>({
data,
pagination: Mock.of<ShlinkPaginator>({ pagination: Mock.of<ShlinkPaginator>({
totalItems: 15, totalItems: 15,
}), }),
@ -118,7 +144,7 @@ describe('shortUrlsListReducer', () => {
expect(reducer(state, { type: CREATE_SHORT_URL, result: newShortUrl } as any)).toEqual({ expect(reducer(state, { type: CREATE_SHORT_URL, result: newShortUrl } as any)).toEqual({
shortUrls: { shortUrls: {
data: [{ shortCode: 'newOne' }, { shortCode }, { shortCode, domain: 'example.com' }], data: expectedData,
pagination: { totalItems: 16 }, pagination: { totalItems: 16 },
}, },
loading: false, loading: false,