Added changes to load orphan visits and fixed tests

This commit is contained in:
Alejandro Celaya 2022-02-05 13:37:49 +01:00
parent 60929342fb
commit 8fbe6bb17d
10 changed files with 63 additions and 34 deletions

View file

@ -60,6 +60,10 @@ export default class ShlinkApiClient {
this.performRequest<{ visits: ShlinkVisits }>('/visits/orphan', 'GET', query) this.performRequest<{ visits: ShlinkVisits }>('/visits/orphan', 'GET', query)
.then(({ data }) => data.visits); .then(({ data }) => data.visits);
public readonly getNonOrphanVisits = async (query?: Omit<ShlinkVisitsParams, 'domain'>): Promise<ShlinkVisits> =>
this.performRequest<{ visits: ShlinkVisits }>('/visits/non-orphan', 'GET', query)
.then(({ data }) => data.visits);
public readonly getVisitsOverview = async (): Promise<ShlinkVisitsOverview> => public readonly getVisitsOverview = async (): Promise<ShlinkVisitsOverview> =>
this.performRequest<{ visits: ShlinkVisitsOverview }>('/visits', 'GET') this.performRequest<{ visits: ShlinkVisitsOverview }>('/visits', 'GET')
.then(({ data }) => data.visits); .then(({ data }) => data.visits);

View file

@ -19,6 +19,7 @@ const MenuLayout = (
ShortUrlVisits: FC, ShortUrlVisits: FC,
TagVisits: FC, TagVisits: FC,
OrphanVisits: FC, OrphanVisits: FC,
NonOrphanVisits: FC,
ServerError: FC, ServerError: FC,
Overview: FC, Overview: FC,
EditShortUrl: FC, EditShortUrl: FC,
@ -56,7 +57,7 @@ const MenuLayout = (
<Route path="/server/:serverId/short-code/:shortCode/edit" component={EditShortUrl} /> <Route path="/server/:serverId/short-code/:shortCode/edit" component={EditShortUrl} />
<Route path="/server/:serverId/tag/:tag/visits" component={TagVisits} /> <Route path="/server/:serverId/tag/:tag/visits" component={TagVisits} />
{addOrphanVisitsRoute && <Route path="/server/:serverId/orphan-visits" component={OrphanVisits} />} {addOrphanVisitsRoute && <Route path="/server/:serverId/orphan-visits" component={OrphanVisits} />}
{addNonOrphanVisitsRoute && <Route path="/server/:serverId/visits" render={() => 'Non orphan'} />} {addNonOrphanVisitsRoute && <Route path="/server/:serverId/non-orphan-visits" component={NonOrphanVisits} />}
<Route exact path="/server/:serverId/manage-tags" component={TagsList} /> <Route exact path="/server/:serverId/manage-tags" component={TagsList} />
{addManageDomainsRoute && <Route exact path="/server/:serverId/manage-domains" component={ManageDomains} />} {addManageDomainsRoute && <Route exact path="/server/:serverId/manage-domains" component={ManageDomains} />}
<Route <Route

View file

@ -25,6 +25,7 @@ export interface ShlinkState {
shortUrlVisits: ShortUrlVisits; shortUrlVisits: ShortUrlVisits;
tagVisits: TagVisits; tagVisits: TagVisits;
orphanVisits: VisitsInfo; orphanVisits: VisitsInfo;
nonOrphanVisits: VisitsInfo;
shortUrlDetail: ShortUrlDetail; shortUrlDetail: ShortUrlDetail;
tagsList: TagsList; tagsList: TagsList;
tagDelete: TagDeletion; tagDelete: TagDeletion;

View file

@ -8,6 +8,7 @@ import shortUrlEditionReducer from '../short-urls/reducers/shortUrlEdition';
import shortUrlVisitsReducer from '../visits/reducers/shortUrlVisits'; import shortUrlVisitsReducer from '../visits/reducers/shortUrlVisits';
import tagVisitsReducer from '../visits/reducers/tagVisits'; import tagVisitsReducer from '../visits/reducers/tagVisits';
import orphanVisitsReducer from '../visits/reducers/orphanVisits'; import orphanVisitsReducer from '../visits/reducers/orphanVisits';
import nonOrphanVisitsReducer from '../visits/reducers/nonOrphanVisits';
import shortUrlDetailReducer from '../short-urls/reducers/shortUrlDetail'; import shortUrlDetailReducer from '../short-urls/reducers/shortUrlDetail';
import tagsListReducer from '../tags/reducers/tagsList'; import tagsListReducer from '../tags/reducers/tagsList';
import tagDeleteReducer from '../tags/reducers/tagDelete'; import tagDeleteReducer from '../tags/reducers/tagDelete';
@ -29,6 +30,7 @@ export default combineReducers<ShlinkState>({
shortUrlVisits: shortUrlVisitsReducer, shortUrlVisits: shortUrlVisitsReducer,
tagVisits: tagVisitsReducer, tagVisits: tagVisitsReducer,
orphanVisits: orphanVisitsReducer, orphanVisits: orphanVisitsReducer,
nonOrphanVisits: nonOrphanVisitsReducer,
shortUrlDetail: shortUrlDetailReducer, shortUrlDetail: shortUrlDetailReducer,
tagsList: tagsListReducer, tagsList: tagsListReducer,
tagDelete: tagDeleteReducer, tagDelete: tagDeleteReducer,

View file

@ -56,15 +56,12 @@ export const Overview = (
<> <>
<Row> <Row>
<div className="col-lg-6 col-xl-3 mb-3"> <div className="col-lg-6 col-xl-3 mb-3">
<HighlightCard title="Visits" link={linkToNonOrphanVisits ? `/server/${serverId}/visits` : undefined}> <HighlightCard title="Visits" link={linkToNonOrphanVisits && `/server/${serverId}/non-orphan-visits`}>
{loadingVisits ? 'Loading...' : prettify(visitsCount)} {loadingVisits ? 'Loading...' : prettify(visitsCount)}
</HighlightCard> </HighlightCard>
</div> </div>
<div className="col-lg-6 col-xl-3 mb-3"> <div className="col-lg-6 col-xl-3 mb-3">
<HighlightCard <HighlightCard title="Orphan visits" link={linkToOrphanVisits && `/server/${serverId}/orphan-visits`}>
title="Orphan visits"
link={linkToOrphanVisits ? `/server/${serverId}/orphan-visits` : undefined}
>
<ForServerVersion minVersion="2.6.0"> <ForServerVersion minVersion="2.6.0">
{loadingVisits ? 'Loading...' : prettify(orphanVisitsCount ?? 0)} {loadingVisits ? 'Loading...' : prettify(orphanVisitsCount ?? 0)}
</ForServerVersion> </ForServerVersion>

View file

@ -7,10 +7,10 @@ import './HighlightCard.scss';
export interface HighlightCardProps { export interface HighlightCardProps {
title: string; title: string;
link?: string; link?: string | false;
} }
const buildExtraProps = (link?: string) => !link ? {} : { tag: Link, to: link }; const buildExtraProps = (link?: string | false) => !link ? {} : { tag: Link, to: link };
export const HighlightCard: FC<HighlightCardProps> = ({ children, title, link }) => ( export const HighlightCard: FC<HighlightCardProps> = ({ children, title, link }) => (
<Card className="highlight-card" body {...buildExtraProps(link)}> <Card className="highlight-card" body {...buildExtraProps(link)}>

View file

@ -313,6 +313,20 @@ describe('ShlinkApiClient', () => {
}); });
}); });
describe('getNonOrphanVisits', () => {
it('returns non-orphan visits', async () => {
const expectedData: Visit[] = [];
const resp = { visits: expectedData };
const axiosSpy = createAxiosMock({ data: resp });
const { getNonOrphanVisits } = new ShlinkApiClient(axiosSpy, '', '');
const result = await getNonOrphanVisits();
expect(axiosSpy).toHaveBeenCalled();
expect(result).toEqual(expectedData);
});
});
describe('editDomainRedirects', () => { describe('editDomainRedirects', () => {
it('returns the redirects', async () => { it('returns the redirects', async () => {
const resp = { baseUrlRedirect: null, regular404Redirect: 'foo', invalidShortUrlRedirect: 'bar' }; const resp = { baseUrlRedirect: null, regular404Redirect: 'foo', invalidShortUrlRedirect: 'bar' };

View file

@ -11,7 +11,7 @@ import { SemVer } from '../../src/utils/helpers/version';
describe('<MenuLayout />', () => { describe('<MenuLayout />', () => {
const ServerError = jest.fn(); const ServerError = jest.fn();
const C = jest.fn(); const C = jest.fn();
const MenuLayout = createMenuLayout(C, C, C, C, C, C, C, ServerError, C, C, C); const MenuLayout = createMenuLayout(C, C, C, C, C, C, C, C, ServerError, C, C, C);
let wrapper: ShallowWrapper; let wrapper: ShallowWrapper;
const createWrapper = (selectedServer: SelectedServer) => { const createWrapper = (selectedServer: SelectedServer) => {
wrapper = shallow( wrapper = shallow(
@ -52,6 +52,9 @@ describe('<MenuLayout />', () => {
[ '2.5.0' as SemVer, 8 ], [ '2.5.0' as SemVer, 8 ],
[ '2.6.0' as SemVer, 9 ], [ '2.6.0' as SemVer, 9 ],
[ '2.7.0' as SemVer, 9 ], [ '2.7.0' as SemVer, 9 ],
[ '2.8.0' as SemVer, 10 ],
[ '2.10.0' as SemVer, 10 ],
[ '3.0.0' as SemVer, 11 ],
])('has expected amount of routes based on selected server\'s version', (version, expectedAmountOfRoutes) => { ])('has expected amount of routes based on selected server\'s version', (version, expectedAmountOfRoutes) => {
const selectedServer = Mock.of<ReachableServer>({ version }); const selectedServer = Mock.of<ReachableServer>({ version });
const wrapper = createWrapper(selectedServer).dive(); const wrapper = createWrapper(selectedServer).dive();

View file

@ -1,8 +1,7 @@
import { FC } from 'react'; import { FC } from 'react';
import { shallow, ShallowWrapper } from 'enzyme'; import { mount, ReactWrapper } from 'enzyme';
import { Mock } from 'ts-mockery'; import { Mock } from 'ts-mockery';
import { CardText } from 'reactstrap'; import { Link, MemoryRouter } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { ShortUrlsList as ShortUrlsListState } from '../../src/short-urls/reducers/shortUrlsList'; import { ShortUrlsList as ShortUrlsListState } from '../../src/short-urls/reducers/shortUrlsList';
import { Overview as overviewCreator } from '../../src/servers/Overview'; import { Overview as overviewCreator } from '../../src/servers/Overview';
import { TagsList } from '../../src/tags/reducers/tagsList'; import { TagsList } from '../../src/tags/reducers/tagsList';
@ -10,9 +9,10 @@ import { VisitsOverview } from '../../src/visits/reducers/visitsOverview';
import { MercureInfo } from '../../src/mercure/reducers/mercureInfo'; import { MercureInfo } from '../../src/mercure/reducers/mercureInfo';
import { ReachableServer } from '../../src/servers/data'; import { ReachableServer } from '../../src/servers/data';
import { prettify } from '../../src/utils/helpers/numbers'; import { prettify } from '../../src/utils/helpers/numbers';
import { HighlightCard } from '../../src/servers/helpers/HighlightCard';
describe('<Overview />', () => { describe('<Overview />', () => {
let wrapper: ShallowWrapper; let wrapper: ReactWrapper;
const ShortUrlsTable = () => null; const ShortUrlsTable = () => null;
const CreateShortUrl = () => null; const CreateShortUrl = () => null;
const ForServerVersion: FC = ({ children }) => <>{children}</>; const ForServerVersion: FC = ({ children }) => <>{children}</>;
@ -25,20 +25,22 @@ describe('<Overview />', () => {
}; };
const serverId = '123'; const serverId = '123';
const createWrapper = (loading = false) => { const createWrapper = (loading = false) => {
wrapper = shallow( wrapper = mount(
<Overview <MemoryRouter>
listShortUrls={listShortUrls} <Overview
listTags={listTags} listShortUrls={listShortUrls}
loadVisitsOverview={loadVisitsOverview} listTags={listTags}
shortUrlsList={Mock.of<ShortUrlsListState>({ loading, shortUrls })} loadVisitsOverview={loadVisitsOverview}
tagsList={Mock.of<TagsList>({ loading, tags: [ 'foo', 'bar', 'baz' ] })} shortUrlsList={Mock.of<ShortUrlsListState>({ loading, shortUrls })}
visitsOverview={Mock.of<VisitsOverview>({ loading, visitsCount: 3456, orphanVisitsCount: 28 })} tagsList={Mock.of<TagsList>({ loading, tags: [ 'foo', 'bar', 'baz' ] })}
selectedServer={Mock.of<ReachableServer>({ id: serverId })} visitsOverview={Mock.of<VisitsOverview>({ loading, visitsCount: 3456, orphanVisitsCount: 28 })}
createNewVisits={jest.fn()} selectedServer={Mock.of<ReachableServer>({ id: serverId })}
loadMercureInfo={jest.fn()} createNewVisits={jest.fn()}
mercureInfo={Mock.all<MercureInfo>()} loadMercureInfo={jest.fn()}
/>, mercureInfo={Mock.all<MercureInfo>()}
).dive(); // Dive is needed as this component is wrapped in a HOC />
</MemoryRouter>,
);
return wrapper; return wrapper;
}; };
@ -47,7 +49,7 @@ describe('<Overview />', () => {
it('displays loading messages when still loading', () => { it('displays loading messages when still loading', () => {
const wrapper = createWrapper(true); const wrapper = createWrapper(true);
const cards = wrapper.find(CardText); const cards = wrapper.find(HighlightCard);
expect(cards).toHaveLength(4); expect(cards).toHaveLength(4);
cards.forEach((card) => expect(card.html()).toContain('Loading...')); cards.forEach((card) => expect(card.html()).toContain('Loading...'));
@ -55,7 +57,7 @@ describe('<Overview />', () => {
it('displays amounts in cards after finishing loading', () => { it('displays amounts in cards after finishing loading', () => {
const wrapper = createWrapper(); const wrapper = createWrapper();
const cards = wrapper.find(CardText); const cards = wrapper.find(HighlightCard);
expect(cards).toHaveLength(4); expect(cards).toHaveLength(4);
expect(cards.at(0).html()).toContain(prettify(3456)); expect(cards.at(0).html()).toContain(prettify(3456));
@ -75,8 +77,10 @@ describe('<Overview />', () => {
const wrapper = createWrapper(); const wrapper = createWrapper();
const links = wrapper.find(Link); const links = wrapper.find(Link);
expect(links).toHaveLength(2); expect(links).toHaveLength(4);
expect(links.at(0).prop('to')).toEqual(`/server/${serverId}/create-short-url`); expect(links.at(0).prop('to')).toEqual(`/server/${serverId}/list-short-urls/1`);
expect(links.at(1).prop('to')).toEqual(`/server/${serverId}/list-short-urls/1`); expect(links.at(1).prop('to')).toEqual(`/server/${serverId}/manage-tags`);
expect(links.at(2).prop('to')).toEqual(`/server/${serverId}/create-short-url`);
expect(links.at(3).prop('to')).toEqual(`/server/${serverId}/list-short-urls/1`);
}); });
}); });

View file

@ -15,8 +15,11 @@ describe('<HighlightCard />', () => {
afterEach(() => wrapper?.unmount()); afterEach(() => wrapper?.unmount());
it('renders expected components', () => { it.each([
const wrapper = createWrapper({ title: 'foo' }); [ undefined ],
[ false ],
])('renders expected components', (link) => {
const wrapper = createWrapper({ title: 'foo', link: link as undefined | false });
expect(wrapper.find(Card)).toHaveLength(1); expect(wrapper.find(Card)).toHaveLength(1);
expect(wrapper.find(CardTitle)).toHaveLength(1); expect(wrapper.find(CardTitle)).toHaveLength(1);