mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Created components and reducer to handle non-orphan visits
This commit is contained in:
parent
0608d3cf19
commit
4a80f224d8
6 changed files with 162 additions and 4 deletions
|
@ -41,6 +41,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator, withRouter:
|
|||
'ShortUrlVisits',
|
||||
'TagVisits',
|
||||
'OrphanVisits',
|
||||
'NonOrphanVisits',
|
||||
'ServerError',
|
||||
'Overview',
|
||||
'EditShortUrl',
|
||||
|
|
44
src/visits/NonOrphanVisits.tsx
Normal file
44
src/visits/NonOrphanVisits.tsx
Normal file
|
@ -0,0 +1,44 @@
|
|||
import { RouteComponentProps } from 'react-router';
|
||||
import { boundToMercureHub } from '../mercure/helpers/boundToMercureHub';
|
||||
import { ShlinkVisitsParams } from '../api/types';
|
||||
import { Topics } from '../mercure/helpers/Topics';
|
||||
import VisitsStats from './VisitsStats';
|
||||
import { NormalizedVisit, VisitsInfo, VisitsParams } from './types';
|
||||
import { VisitsExporter } from './services/VisitsExporter';
|
||||
import { CommonVisitsProps } from './types/CommonVisitsProps';
|
||||
import { toApiParams } from './types/helpers';
|
||||
import { NonOrphanVisitsHeader } from './NonOrphanVisitsHeader';
|
||||
|
||||
export interface NonOrphanVisitsProps extends CommonVisitsProps, RouteComponentProps {
|
||||
getNonOrphanVisits: (params?: ShlinkVisitsParams, doIntervalFallback?: boolean) => void;
|
||||
nonOrphanVisits: VisitsInfo;
|
||||
cancelGetNonOrphanVisits: () => void;
|
||||
}
|
||||
|
||||
export const NonOrphanVisits = ({ exportVisits }: VisitsExporter) => boundToMercureHub(({
|
||||
history: { goBack },
|
||||
match: { url },
|
||||
getNonOrphanVisits,
|
||||
nonOrphanVisits,
|
||||
cancelGetNonOrphanVisits,
|
||||
settings,
|
||||
selectedServer,
|
||||
}: NonOrphanVisitsProps) => {
|
||||
const exportCsv = (visits: NormalizedVisit[]) => exportVisits('non_orphan_visits.csv', visits);
|
||||
const loadVisits = (params: VisitsParams, doIntervalFallback?: boolean) =>
|
||||
getNonOrphanVisits(toApiParams(params), doIntervalFallback);
|
||||
|
||||
return (
|
||||
<VisitsStats
|
||||
getVisits={loadVisits}
|
||||
cancelGetVisits={cancelGetNonOrphanVisits}
|
||||
visitsInfo={nonOrphanVisits}
|
||||
baseUrl={url}
|
||||
settings={settings}
|
||||
exportCsv={exportCsv}
|
||||
selectedServer={selectedServer}
|
||||
>
|
||||
<NonOrphanVisitsHeader nonOrphanVisits={nonOrphanVisits} goBack={goBack} />
|
||||
</VisitsStats>
|
||||
);
|
||||
}, () => [ Topics.visits ]);
|
14
src/visits/NonOrphanVisitsHeader.tsx
Normal file
14
src/visits/NonOrphanVisitsHeader.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
import VisitsHeader from './VisitsHeader';
|
||||
import { VisitsInfo } from './types';
|
||||
import './ShortUrlVisitsHeader.scss';
|
||||
|
||||
interface NonOrphanVisitsHeaderProps {
|
||||
nonOrphanVisits: VisitsInfo;
|
||||
goBack: () => void;
|
||||
}
|
||||
|
||||
export const NonOrphanVisitsHeader = ({ nonOrphanVisits, goBack }: NonOrphanVisitsHeaderProps) => {
|
||||
const { visits } = nonOrphanVisits;
|
||||
|
||||
return <VisitsHeader title="Non-orphan visits" goBack={goBack} visits={visits} />;
|
||||
};
|
88
src/visits/reducers/nonOrphanVisits.ts
Normal file
88
src/visits/reducers/nonOrphanVisits.ts
Normal file
|
@ -0,0 +1,88 @@
|
|||
import { Action, Dispatch } from 'redux';
|
||||
import {
|
||||
Visit,
|
||||
VisitsFallbackIntervalAction,
|
||||
VisitsInfo,
|
||||
VisitsLoadProgressChangedAction,
|
||||
} from '../types';
|
||||
import { buildActionCreator, buildReducer } from '../../utils/helpers/redux';
|
||||
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
||||
import { GetState } from '../../container/types';
|
||||
import { ShlinkVisitsParams } from '../../api/types';
|
||||
import { ApiErrorAction } from '../../api/types/actions';
|
||||
import { isBetween } from '../../utils/helpers/date';
|
||||
import { getVisitsWithLoader, lastVisitLoaderForLoader } from './common';
|
||||
import { CREATE_VISITS, CreateVisitsAction } from './visitCreation';
|
||||
|
||||
/* eslint-disable padding-line-between-statements */
|
||||
export const GET_NON_ORPHAN_VISITS_START = 'shlink/orphanVisits/GET_NON_ORPHAN_VISITS_START';
|
||||
export const GET_NON_ORPHAN_VISITS_ERROR = 'shlink/orphanVisits/GET_NON_ORPHAN_VISITS_ERROR';
|
||||
export const GET_NON_ORPHAN_VISITS = 'shlink/orphanVisits/GET_NON_ORPHAN_VISITS';
|
||||
export const GET_NON_ORPHAN_VISITS_LARGE = 'shlink/orphanVisits/GET_NON_ORPHAN_VISITS_LARGE';
|
||||
export const GET_NON_ORPHAN_VISITS_CANCEL = 'shlink/orphanVisits/GET_NON_ORPHAN_VISITS_CANCEL';
|
||||
export const GET_NON_ORPHAN_VISITS_PROGRESS_CHANGED = 'shlink/orphanVisits/GET_NON_ORPHAN_VISITS_PROGRESS_CHANGED';
|
||||
export const GET_NON_ORPHAN_VISITS_FALLBACK_TO_INTERVAL = 'shlink/orphanVisits/GET_NON_ORPHAN_VISITS_FALLBACK_TO_INTERVAL';
|
||||
/* eslint-enable padding-line-between-statements */
|
||||
|
||||
export interface NonOrphanVisitsAction extends Action<string> {
|
||||
visits: Visit[];
|
||||
query?: ShlinkVisitsParams;
|
||||
}
|
||||
|
||||
type NonOrphanVisitsCombinedAction = NonOrphanVisitsAction
|
||||
& VisitsLoadProgressChangedAction
|
||||
& VisitsFallbackIntervalAction
|
||||
& CreateVisitsAction
|
||||
& ApiErrorAction;
|
||||
|
||||
const initialState: VisitsInfo = {
|
||||
visits: [],
|
||||
loading: false,
|
||||
loadingLarge: false,
|
||||
error: false,
|
||||
cancelLoad: false,
|
||||
progress: 0,
|
||||
};
|
||||
|
||||
export default buildReducer<VisitsInfo, NonOrphanVisitsCombinedAction>({
|
||||
[GET_NON_ORPHAN_VISITS_START]: () => ({ ...initialState, loading: true }),
|
||||
[GET_NON_ORPHAN_VISITS_ERROR]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
|
||||
[GET_NON_ORPHAN_VISITS]: (state, { visits, query }) => ({ ...state, visits, query, loading: false, error: false }),
|
||||
[GET_NON_ORPHAN_VISITS_LARGE]: (state) => ({ ...state, loadingLarge: true }),
|
||||
[GET_NON_ORPHAN_VISITS_CANCEL]: (state) => ({ ...state, cancelLoad: true }),
|
||||
[GET_NON_ORPHAN_VISITS_PROGRESS_CHANGED]: (state, { progress }) => ({ ...state, progress }),
|
||||
[GET_NON_ORPHAN_VISITS_FALLBACK_TO_INTERVAL]: (state, { fallbackInterval }) => ({ ...state, fallbackInterval }),
|
||||
[CREATE_VISITS]: (state, { createdVisits }) => {
|
||||
const { visits, query = {} } = state;
|
||||
const { startDate, endDate } = query;
|
||||
const newVisits = createdVisits
|
||||
.filter(({ visit }) => isBetween(visit.date, startDate, endDate))
|
||||
.map(({ visit }) => visit);
|
||||
|
||||
return { ...state, visits: [ ...newVisits, ...visits ] };
|
||||
},
|
||||
}, initialState);
|
||||
|
||||
export const getNonOrphanVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => (
|
||||
query: ShlinkVisitsParams = {},
|
||||
doIntervalFallback = false,
|
||||
) => async (dispatch: Dispatch, getState: GetState) => {
|
||||
const { getNonOrphanVisits } = buildShlinkApiClient(getState);
|
||||
const visitsLoader = async (page: number, itemsPerPage: number) =>
|
||||
getNonOrphanVisits({ ...query, page, itemsPerPage });
|
||||
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, getNonOrphanVisits);
|
||||
const shouldCancel = () => getState().orphanVisits.cancelLoad;
|
||||
const extraFinishActionData: Partial<NonOrphanVisitsAction> = { query };
|
||||
const actionMap = {
|
||||
start: GET_NON_ORPHAN_VISITS_START,
|
||||
large: GET_NON_ORPHAN_VISITS_LARGE,
|
||||
finish: GET_NON_ORPHAN_VISITS,
|
||||
error: GET_NON_ORPHAN_VISITS_ERROR,
|
||||
progress: GET_NON_ORPHAN_VISITS_PROGRESS_CHANGED,
|
||||
fallbackToInterval: GET_NON_ORPHAN_VISITS_FALLBACK_TO_INTERVAL,
|
||||
};
|
||||
|
||||
return getVisitsWithLoader(visitsLoader, lastVisitLoader, extraFinishActionData, actionMap, dispatch, shouldCancel);
|
||||
};
|
||||
|
||||
export const cancelGetNonOrphanVisits = buildActionCreator(GET_NON_ORPHAN_VISITS_CANCEL);
|
|
@ -59,7 +59,7 @@ export default buildReducer<VisitsInfo, OrphanVisitsCombinedAction>({
|
|||
const { visits, query = {} } = state;
|
||||
const { startDate, endDate } = query;
|
||||
const newVisits = createdVisits
|
||||
.filter(({ visit }) => isBetween(visit.date, startDate, endDate))
|
||||
.filter(({ visit, shortUrl }) => !shortUrl && isBetween(visit.date, startDate, endDate))
|
||||
.map(({ visit }) => visit);
|
||||
|
||||
return { ...state, visits: [ ...newVisits, ...visits ] };
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import Bottle from 'bottlejs';
|
||||
import ShortUrlVisits from '../ShortUrlVisits';
|
||||
import { cancelGetShortUrlVisits, getShortUrlVisits } from '../reducers/shortUrlVisits';
|
||||
import MapModal from '../helpers/MapModal';
|
||||
import { createNewVisits } from '../reducers/visitCreation';
|
||||
import ShortUrlVisits from '../ShortUrlVisits';
|
||||
import TagVisits from '../TagVisits';
|
||||
import { cancelGetTagVisits, getTagVisits } from '../reducers/tagVisits';
|
||||
import { OrphanVisits } from '../OrphanVisits';
|
||||
import { NonOrphanVisits } from '../NonOrphanVisits';
|
||||
import { cancelGetShortUrlVisits, getShortUrlVisits } from '../reducers/shortUrlVisits';
|
||||
import { cancelGetTagVisits, getTagVisits } from '../reducers/tagVisits';
|
||||
import { cancelGetOrphanVisits, getOrphanVisits } from '../reducers/orphanVisits';
|
||||
import { cancelGetNonOrphanVisits, getNonOrphanVisits } from '../reducers/nonOrphanVisits';
|
||||
import { ConnectDecorator } from '../../container/types';
|
||||
import { loadVisitsOverview } from '../reducers/visitsOverview';
|
||||
import * as visitsParser from './VisitsParser';
|
||||
|
@ -34,6 +36,12 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||
[ 'getOrphanVisits', 'cancelGetOrphanVisits', 'createNewVisits', 'loadMercureInfo' ],
|
||||
));
|
||||
|
||||
bottle.serviceFactory('NonOrphanVisits', NonOrphanVisits, 'VisitsExporter');
|
||||
bottle.decorator('NonOrphanVisits', connect(
|
||||
[ 'nonOrphanVisits', 'mercureInfo', 'settings', 'selectedServer' ],
|
||||
[ 'getNonOrphanVisits', 'cancelGetNonOrphanVisits', 'createNewVisits', 'loadMercureInfo' ],
|
||||
));
|
||||
|
||||
// Services
|
||||
bottle.serviceFactory('VisitsParser', () => visitsParser);
|
||||
bottle.service('VisitsExporter', VisitsExporter, 'window', 'csvjson');
|
||||
|
@ -48,6 +56,9 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
|||
bottle.serviceFactory('getOrphanVisits', getOrphanVisits, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('cancelGetOrphanVisits', () => cancelGetOrphanVisits);
|
||||
|
||||
bottle.serviceFactory('getNonOrphanVisits', getNonOrphanVisits, 'buildShlinkApiClient');
|
||||
bottle.serviceFactory('cancelGetNonOrphanVisits', () => cancelGetNonOrphanVisits);
|
||||
|
||||
bottle.serviceFactory('createNewVisits', () => createNewVisits);
|
||||
bottle.serviceFactory('loadVisitsOverview', loadVisitsOverview, 'buildShlinkApiClient');
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue