mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 17:40:23 +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',
|
'ShortUrlVisits',
|
||||||
'TagVisits',
|
'TagVisits',
|
||||||
'OrphanVisits',
|
'OrphanVisits',
|
||||||
|
'NonOrphanVisits',
|
||||||
'ServerError',
|
'ServerError',
|
||||||
'Overview',
|
'Overview',
|
||||||
'EditShortUrl',
|
'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 { visits, query = {} } = state;
|
||||||
const { startDate, endDate } = query;
|
const { startDate, endDate } = query;
|
||||||
const newVisits = createdVisits
|
const newVisits = createdVisits
|
||||||
.filter(({ visit }) => isBetween(visit.date, startDate, endDate))
|
.filter(({ visit, shortUrl }) => !shortUrl && isBetween(visit.date, startDate, endDate))
|
||||||
.map(({ visit }) => visit);
|
.map(({ visit }) => visit);
|
||||||
|
|
||||||
return { ...state, visits: [ ...newVisits, ...visits ] };
|
return { ...state, visits: [ ...newVisits, ...visits ] };
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import Bottle from 'bottlejs';
|
import Bottle from 'bottlejs';
|
||||||
import ShortUrlVisits from '../ShortUrlVisits';
|
|
||||||
import { cancelGetShortUrlVisits, getShortUrlVisits } from '../reducers/shortUrlVisits';
|
|
||||||
import MapModal from '../helpers/MapModal';
|
import MapModal from '../helpers/MapModal';
|
||||||
import { createNewVisits } from '../reducers/visitCreation';
|
import { createNewVisits } from '../reducers/visitCreation';
|
||||||
|
import ShortUrlVisits from '../ShortUrlVisits';
|
||||||
import TagVisits from '../TagVisits';
|
import TagVisits from '../TagVisits';
|
||||||
import { cancelGetTagVisits, getTagVisits } from '../reducers/tagVisits';
|
|
||||||
import { OrphanVisits } from '../OrphanVisits';
|
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 { cancelGetOrphanVisits, getOrphanVisits } from '../reducers/orphanVisits';
|
||||||
|
import { cancelGetNonOrphanVisits, getNonOrphanVisits } from '../reducers/nonOrphanVisits';
|
||||||
import { ConnectDecorator } from '../../container/types';
|
import { ConnectDecorator } from '../../container/types';
|
||||||
import { loadVisitsOverview } from '../reducers/visitsOverview';
|
import { loadVisitsOverview } from '../reducers/visitsOverview';
|
||||||
import * as visitsParser from './VisitsParser';
|
import * as visitsParser from './VisitsParser';
|
||||||
|
@ -34,6 +36,12 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
[ 'getOrphanVisits', 'cancelGetOrphanVisits', 'createNewVisits', 'loadMercureInfo' ],
|
[ 'getOrphanVisits', 'cancelGetOrphanVisits', 'createNewVisits', 'loadMercureInfo' ],
|
||||||
));
|
));
|
||||||
|
|
||||||
|
bottle.serviceFactory('NonOrphanVisits', NonOrphanVisits, 'VisitsExporter');
|
||||||
|
bottle.decorator('NonOrphanVisits', connect(
|
||||||
|
[ 'nonOrphanVisits', 'mercureInfo', 'settings', 'selectedServer' ],
|
||||||
|
[ 'getNonOrphanVisits', 'cancelGetNonOrphanVisits', 'createNewVisits', 'loadMercureInfo' ],
|
||||||
|
));
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
bottle.serviceFactory('VisitsParser', () => visitsParser);
|
bottle.serviceFactory('VisitsParser', () => visitsParser);
|
||||||
bottle.service('VisitsExporter', VisitsExporter, 'window', 'csvjson');
|
bottle.service('VisitsExporter', VisitsExporter, 'window', 'csvjson');
|
||||||
|
@ -48,6 +56,9 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
|
||||||
bottle.serviceFactory('getOrphanVisits', getOrphanVisits, 'buildShlinkApiClient');
|
bottle.serviceFactory('getOrphanVisits', getOrphanVisits, 'buildShlinkApiClient');
|
||||||
bottle.serviceFactory('cancelGetOrphanVisits', () => cancelGetOrphanVisits);
|
bottle.serviceFactory('cancelGetOrphanVisits', () => cancelGetOrphanVisits);
|
||||||
|
|
||||||
|
bottle.serviceFactory('getNonOrphanVisits', getNonOrphanVisits, 'buildShlinkApiClient');
|
||||||
|
bottle.serviceFactory('cancelGetNonOrphanVisits', () => cancelGetNonOrphanVisits);
|
||||||
|
|
||||||
bottle.serviceFactory('createNewVisits', () => createNewVisits);
|
bottle.serviceFactory('createNewVisits', () => createNewVisits);
|
||||||
bottle.serviceFactory('loadVisitsOverview', loadVisitsOverview, 'buildShlinkApiClient');
|
bottle.serviceFactory('loadVisitsOverview', loadVisitsOverview, 'buildShlinkApiClient');
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue