mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 01:20:24 +03:00
Migrated visits-loading actions to payload actions
This commit is contained in:
parent
a6ed0c811d
commit
a3cc3d5fc2
15 changed files with 136 additions and 127 deletions
|
@ -17,9 +17,6 @@ export const buildReducer = <State, AT extends Action>(map: ActionHandlerMap<Sta
|
|||
return actionHandler ? actionHandler(currentState, action) : currentState;
|
||||
};
|
||||
|
||||
/** @deprecated */
|
||||
export const buildActionCreator = <T extends string>(type: T) => (): Action<T> => ({ type });
|
||||
|
||||
export const createAsyncThunk = <Returned, ThunkArg>(
|
||||
typePrefix: string,
|
||||
payloadCreator: AsyncThunkPayloadCreator<Returned, ThunkArg, { state: ShlinkState }>,
|
||||
|
|
|
@ -5,7 +5,7 @@ import { ShlinkVisitsParams } from '../api/types';
|
|||
import { Topics } from '../mercure/helpers/Topics';
|
||||
import { useGoBack } from '../utils/helpers/hooks';
|
||||
import { ReportExporter } from '../common/services/ReportExporter';
|
||||
import { TagVisits as TagVisitsState } from './reducers/tagVisits';
|
||||
import { LoadTagVisits, TagVisits as TagVisitsState } from './reducers/tagVisits';
|
||||
import { TagVisitsHeader } from './TagVisitsHeader';
|
||||
import { VisitsStats } from './VisitsStats';
|
||||
import { NormalizedVisit } from './types';
|
||||
|
@ -13,7 +13,7 @@ import { CommonVisitsProps } from './types/CommonVisitsProps';
|
|||
import { toApiParams } from './types/helpers';
|
||||
|
||||
export interface TagVisitsProps extends CommonVisitsProps {
|
||||
getTagVisits: (tag: string, query?: ShlinkVisitsParams, doIntervalFallback?: boolean) => void;
|
||||
getTagVisits: (params: LoadTagVisits) => void;
|
||||
tagVisits: TagVisitsState;
|
||||
cancelGetTagVisits: () => void;
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export const TagVisits = (colorGenerator: ColorGenerator, { exportVisits }: Repo
|
|||
const goBack = useGoBack();
|
||||
const { tag = '' } = useParams();
|
||||
const loadVisits = (params: ShlinkVisitsParams, doIntervalFallback?: boolean) =>
|
||||
getTagVisits(tag, toApiParams(params), doIntervalFallback);
|
||||
getTagVisits({ tag, query: toApiParams(params), doIntervalFallback });
|
||||
const exportCsv = (visits: NormalizedVisit[]) => exportVisits(`tag_${tag}_visits.csv`, visits);
|
||||
|
||||
return (
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { flatten, prop, range, splitEvery } from 'ramda';
|
||||
import { Action, Dispatch } from 'redux';
|
||||
import { Dispatch } from '@reduxjs/toolkit';
|
||||
import { ShlinkPaginator, ShlinkVisits, ShlinkVisitsParams } from '../../api/types';
|
||||
import { Visit } from '../types';
|
||||
import { parseApiError } from '../../api/utils';
|
||||
import { ApiErrorAction } from '../../api/types/actions';
|
||||
import { dateToMatchingInterval } from '../../utils/dates/types';
|
||||
import { VisitsLoadProgressChangedAction } from './types';
|
||||
import { VisitsLoaded, VisitsLoadProgressChangedAction } from './types';
|
||||
|
||||
const ITEMS_PER_PAGE = 5000;
|
||||
const PARALLEL_REQUESTS_COUNT = 4;
|
||||
|
@ -17,10 +17,10 @@ const calcProgress = (total: number, current: number): number => (current * 100)
|
|||
type VisitsLoader = (page: number, itemsPerPage: number) => Promise<ShlinkVisits>;
|
||||
type LastVisitLoader = () => Promise<Visit | undefined>;
|
||||
|
||||
export const getVisitsWithLoader = async <T extends Action<string> & { visits: Visit[] }>(
|
||||
export const getVisitsWithLoader = async <T extends VisitsLoaded>(
|
||||
visitsLoader: VisitsLoader,
|
||||
lastVisitLoader: LastVisitLoader,
|
||||
extraFinishActionData: Partial<T>,
|
||||
extraFulfilledPayload: Partial<T>,
|
||||
actionsPrefix: string,
|
||||
dispatch: Dispatch,
|
||||
shouldCancel: () => boolean,
|
||||
|
@ -74,7 +74,7 @@ export const getVisitsWithLoader = async <T extends Action<string> & { visits: V
|
|||
dispatch(
|
||||
!visits.length && lastVisit
|
||||
? { type: `${actionsPrefix}/fallbackToInterval`, payload: dateToMatchingInterval(lastVisit.date) }
|
||||
: { ...extraFinishActionData, visits, type: `${actionsPrefix}/fulfilled` },
|
||||
: { type: `${actionsPrefix}/fulfilled`, payload: { ...extraFulfilledPayload, visits } },
|
||||
);
|
||||
} catch (e: any) {
|
||||
dispatch<ApiErrorAction>({ type: `${actionsPrefix}/rejected`, errorData: parseApiError(e) });
|
||||
|
@ -89,5 +89,5 @@ export const lastVisitLoaderForLoader = (
|
|||
return async () => Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return async () => loader({ page: 1, itemsPerPage: 1 }).then((result) => result.data[0]);
|
||||
return async () => loader({ page: 1, itemsPerPage: 1 }).then(({ data }) => data[0]);
|
||||
};
|
||||
|
|
|
@ -1,16 +1,21 @@
|
|||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { Action, Dispatch } from 'redux';
|
||||
import { Visit } from '../types';
|
||||
import { Dispatch } from 'redux';
|
||||
import { 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 { createNewVisits, CreateVisitsAction } from './visitCreation';
|
||||
import { domainMatches } from '../../short-urls/helpers';
|
||||
import { LoadVisits, VisitsFallbackIntervalAction, VisitsInfo, VisitsLoadProgressChangedAction } from './types';
|
||||
import {
|
||||
LoadVisits,
|
||||
VisitsFallbackIntervalAction,
|
||||
VisitsInfo,
|
||||
VisitsLoaded,
|
||||
VisitsLoadedAction,
|
||||
VisitsLoadProgressChangedAction,
|
||||
} from './types';
|
||||
|
||||
const REDUCER_PREFIX = 'shlink/domainVisits';
|
||||
export const GET_DOMAIN_VISITS_START = `${REDUCER_PREFIX}/getDomainVisits/pending`;
|
||||
|
@ -23,19 +28,15 @@ export const GET_DOMAIN_VISITS_FALLBACK_TO_INTERVAL = `${REDUCER_PREFIX}/getDoma
|
|||
|
||||
export const DEFAULT_DOMAIN = 'DEFAULT';
|
||||
|
||||
export interface DomainVisits extends VisitsInfo {
|
||||
interface WithDomain {
|
||||
domain: string;
|
||||
}
|
||||
|
||||
export interface LoadDomainVisits extends LoadVisits {
|
||||
domain: string;
|
||||
}
|
||||
export interface DomainVisits extends VisitsInfo, WithDomain {}
|
||||
|
||||
export interface DomainVisitsAction extends Action<string> {
|
||||
visits: Visit[];
|
||||
domain: string;
|
||||
query?: ShlinkVisitsParams;
|
||||
}
|
||||
export interface LoadDomainVisits extends LoadVisits, WithDomain {}
|
||||
|
||||
type DomainVisitsAction = VisitsLoadedAction<WithDomain>;
|
||||
|
||||
type DomainVisitsCombinedAction = DomainVisitsAction
|
||||
& VisitsLoadProgressChangedAction
|
||||
|
@ -56,8 +57,8 @@ const initialState: DomainVisits = {
|
|||
export default buildReducer<DomainVisits, DomainVisitsCombinedAction>({
|
||||
[`${REDUCER_PREFIX}/getDomainVisits/pending`]: () => ({ ...initialState, loading: true }),
|
||||
[`${REDUCER_PREFIX}/getDomainVisits/rejected`]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
|
||||
[`${REDUCER_PREFIX}/getDomainVisits/fulfilled`]: (state, { visits, domain, query }) => (
|
||||
{ ...state, visits, domain, query, loading: false, loadingLarge: false, error: false }
|
||||
[`${REDUCER_PREFIX}/getDomainVisits/fulfilled`]: (state, { payload }: DomainVisitsAction) => (
|
||||
{ ...state, ...payload, loading: false, loadingLarge: false, error: false }
|
||||
),
|
||||
[`${REDUCER_PREFIX}/getDomainVisits/large`]: (state) => ({ ...state, loadingLarge: true }),
|
||||
[`${REDUCER_PREFIX}/getDomainVisits/cancel`]: (state) => ({ ...state, cancelLoad: true }),
|
||||
|
@ -87,7 +88,7 @@ export const getDomainVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) =>
|
|||
);
|
||||
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, async (params) => getVisits(domain, params));
|
||||
const shouldCancel = () => getState().domainVisits.cancelLoad;
|
||||
const extraFinishActionData: Partial<DomainVisitsAction> = { domain, query };
|
||||
const extraFinishActionData: Partial<VisitsLoaded<WithDomain>> = { domain, query };
|
||||
const prefix = `${REDUCER_PREFIX}/getDomainVisits`;
|
||||
|
||||
return getVisitsWithLoader(visitsLoader, lastVisitLoader, extraFinishActionData, prefix, dispatch, shouldCancel);
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { Action, Dispatch } from 'redux';
|
||||
import { Visit } from '../types';
|
||||
import { Dispatch } from 'redux';
|
||||
import { 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 { createNewVisits, CreateVisitsAction } from './visitCreation';
|
||||
import { LoadVisits, VisitsFallbackIntervalAction, VisitsInfo, VisitsLoadProgressChangedAction } from './types';
|
||||
import {
|
||||
LoadVisits,
|
||||
VisitsFallbackIntervalAction,
|
||||
VisitsInfo,
|
||||
VisitsLoaded,
|
||||
VisitsLoadedAction,
|
||||
VisitsLoadProgressChangedAction,
|
||||
} from './types';
|
||||
|
||||
const REDUCER_PREFIX = 'shlink/orphanVisits';
|
||||
export const GET_NON_ORPHAN_VISITS_START = `${REDUCER_PREFIX}/getNonOrphanVisits/pending`;
|
||||
|
@ -20,12 +25,7 @@ export const GET_NON_ORPHAN_VISITS_CANCEL = `${REDUCER_PREFIX}/getNonOrphanVisit
|
|||
export const GET_NON_ORPHAN_VISITS_PROGRESS_CHANGED = `${REDUCER_PREFIX}/getNonOrphanVisits/progressChanged`;
|
||||
export const GET_NON_ORPHAN_VISITS_FALLBACK_TO_INTERVAL = `${REDUCER_PREFIX}/getNonOrphanVisits/fallbackToInterval`;
|
||||
|
||||
export interface NonOrphanVisitsAction extends Action<string> {
|
||||
visits: Visit[];
|
||||
query?: ShlinkVisitsParams;
|
||||
}
|
||||
|
||||
type NonOrphanVisitsCombinedAction = NonOrphanVisitsAction
|
||||
type NonOrphanVisitsCombinedAction = VisitsLoadedAction
|
||||
& VisitsLoadProgressChangedAction
|
||||
& VisitsFallbackIntervalAction
|
||||
& CreateVisitsAction
|
||||
|
@ -45,8 +45,8 @@ export default buildReducer<VisitsInfo, NonOrphanVisitsCombinedAction>({
|
|||
[`${REDUCER_PREFIX}/getNonOrphanVisits/rejected`]: (_, { errorData }) => (
|
||||
{ ...initialState, error: true, errorData }
|
||||
),
|
||||
[`${REDUCER_PREFIX}/getNonOrphanVisits/fulfilled`]: (state, { visits, query }) => (
|
||||
{ ...state, visits, query, loading: false, loadingLarge: false, error: false }
|
||||
[`${REDUCER_PREFIX}/getNonOrphanVisits/fulfilled`]: (state, { payload }: VisitsLoadedAction) => (
|
||||
{ ...state, ...payload, loading: false, loadingLarge: false, error: false }
|
||||
),
|
||||
[`${REDUCER_PREFIX}/getNonOrphanVisits/large`]: (state) => ({ ...state, loadingLarge: true }),
|
||||
[`${REDUCER_PREFIX}/getNonOrphanVisits/cancel`]: (state) => ({ ...state, cancelLoad: true }),
|
||||
|
@ -73,7 +73,7 @@ export const getNonOrphanVisits = (buildShlinkApiClient: ShlinkApiClientBuilder)
|
|||
shlinkGetNonOrphanVisits({ ...query, page, itemsPerPage });
|
||||
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, shlinkGetNonOrphanVisits);
|
||||
const shouldCancel = () => getState().orphanVisits.cancelLoad;
|
||||
const extraFinishActionData: Partial<NonOrphanVisitsAction> = { query };
|
||||
const extraFinishActionData: Partial<VisitsLoaded> = { query };
|
||||
const prefix = `${REDUCER_PREFIX}/getNonOrphanVisits`;
|
||||
|
||||
return getVisitsWithLoader(visitsLoader, lastVisitLoader, extraFinishActionData, prefix, dispatch, shouldCancel);
|
||||
|
|
|
@ -1,16 +1,22 @@
|
|||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { Action, Dispatch } from 'redux';
|
||||
import { OrphanVisit, OrphanVisitType, Visit } from '../types';
|
||||
import { Dispatch } from 'redux';
|
||||
import { OrphanVisit, OrphanVisitType } from '../types';
|
||||
import { buildReducer } from '../../utils/helpers/redux';
|
||||
import { ShlinkApiClientBuilder } from '../../api/services/ShlinkApiClientBuilder';
|
||||
import { GetState } from '../../container/types';
|
||||
import { ShlinkVisitsParams } from '../../api/types';
|
||||
import { isOrphanVisit } from '../types/helpers';
|
||||
import { ApiErrorAction } from '../../api/types/actions';
|
||||
import { isBetween } from '../../utils/helpers/date';
|
||||
import { getVisitsWithLoader, lastVisitLoaderForLoader } from './common';
|
||||
import { createNewVisits, CreateVisitsAction } from './visitCreation';
|
||||
import { LoadVisits, VisitsFallbackIntervalAction, VisitsInfo, VisitsLoadProgressChangedAction } from './types';
|
||||
import {
|
||||
LoadVisits,
|
||||
VisitsFallbackIntervalAction,
|
||||
VisitsInfo,
|
||||
VisitsLoaded,
|
||||
VisitsLoadedAction,
|
||||
VisitsLoadProgressChangedAction,
|
||||
} from './types';
|
||||
|
||||
const REDUCER_PREFIX = 'shlink/orphanVisits';
|
||||
export const GET_ORPHAN_VISITS_START = `${REDUCER_PREFIX}/getOrphanVisits/pending`;
|
||||
|
@ -21,16 +27,11 @@ export const GET_ORPHAN_VISITS_CANCEL = `${REDUCER_PREFIX}/getOrphanVisits/cance
|
|||
export const GET_ORPHAN_VISITS_PROGRESS_CHANGED = `${REDUCER_PREFIX}/getOrphanVisits/progressChanged`;
|
||||
export const GET_ORPHAN_VISITS_FALLBACK_TO_INTERVAL = `${REDUCER_PREFIX}/getOrphanVisits/fallbackToInterval`;
|
||||
|
||||
export interface OrphanVisitsAction extends Action<string> {
|
||||
visits: Visit[];
|
||||
query?: ShlinkVisitsParams;
|
||||
}
|
||||
|
||||
export interface LoadOrphanVisits extends LoadVisits {
|
||||
orphanVisitsType?: OrphanVisitType;
|
||||
}
|
||||
|
||||
type OrphanVisitsCombinedAction = OrphanVisitsAction
|
||||
type OrphanVisitsCombinedAction = VisitsLoadedAction
|
||||
& VisitsLoadProgressChangedAction
|
||||
& VisitsFallbackIntervalAction
|
||||
& CreateVisitsAction
|
||||
|
@ -48,8 +49,8 @@ const initialState: VisitsInfo = {
|
|||
export default buildReducer<VisitsInfo, OrphanVisitsCombinedAction>({
|
||||
[`${REDUCER_PREFIX}/getOrphanVisits/pending`]: () => ({ ...initialState, loading: true }),
|
||||
[`${REDUCER_PREFIX}/getOrphanVisits/rejected`]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
|
||||
[`${REDUCER_PREFIX}/getOrphanVisits/fulfilled`]: (state, { visits, query }) => (
|
||||
{ ...state, visits, query, loading: false, loadingLarge: false, error: false }
|
||||
[`${REDUCER_PREFIX}/getOrphanVisits/fulfilled`]: (state, { payload }: VisitsLoadedAction) => (
|
||||
{ ...state, ...payload, loading: false, loadingLarge: false, error: false }
|
||||
),
|
||||
[`${REDUCER_PREFIX}/getOrphanVisits/large`]: (state) => ({ ...state, loadingLarge: true }),
|
||||
[`${REDUCER_PREFIX}/getOrphanVisits/cancel`]: (state) => ({ ...state, cancelLoad: true }),
|
||||
|
@ -83,7 +84,7 @@ export const getOrphanVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) =>
|
|||
});
|
||||
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, getVisits);
|
||||
const shouldCancel = () => getState().orphanVisits.cancelLoad;
|
||||
const extraFinishActionData: Partial<OrphanVisitsAction> = { query };
|
||||
const extraFinishActionData: Partial<VisitsLoaded> = { query };
|
||||
const prefix = `${REDUCER_PREFIX}/getOrphanVisits`;
|
||||
|
||||
return getVisitsWithLoader(visitsLoader, lastVisitLoader, extraFinishActionData, prefix, dispatch, shouldCancel);
|
||||
|
|
|
@ -1,17 +1,22 @@
|
|||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { Action, Dispatch } from 'redux';
|
||||
import { Dispatch } from 'redux';
|
||||
import { shortUrlMatches } from '../../short-urls/helpers';
|
||||
import { Visit } from '../types';
|
||||
import { ShortUrlIdentifier } from '../../short-urls/data';
|
||||
import { 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 { createNewVisits, CreateVisitsAction } from './visitCreation';
|
||||
import { LoadVisits, VisitsFallbackIntervalAction, VisitsInfo, VisitsLoadProgressChangedAction } from './types';
|
||||
import {
|
||||
LoadVisits,
|
||||
VisitsFallbackIntervalAction,
|
||||
VisitsInfo,
|
||||
VisitsLoaded,
|
||||
VisitsLoadedAction,
|
||||
VisitsLoadProgressChangedAction,
|
||||
} from './types';
|
||||
|
||||
const REDUCER_PREFIX = 'shlink/shortUrlVisits';
|
||||
export const GET_SHORT_URL_VISITS_START = `${REDUCER_PREFIX}/getShortUrlVisits/pending`;
|
||||
|
@ -24,10 +29,7 @@ export const GET_SHORT_URL_VISITS_FALLBACK_TO_INTERVAL = `${REDUCER_PREFIX}/getS
|
|||
|
||||
export interface ShortUrlVisits extends VisitsInfo, ShortUrlIdentifier {}
|
||||
|
||||
interface ShortUrlVisitsAction extends Action<string>, ShortUrlIdentifier {
|
||||
visits: Visit[];
|
||||
query?: ShlinkVisitsParams;
|
||||
}
|
||||
type ShortUrlVisitsAction = VisitsLoadedAction<ShortUrlIdentifier>;
|
||||
|
||||
export interface LoadShortUrlVisits extends LoadVisits {
|
||||
shortCode: string;
|
||||
|
@ -53,12 +55,9 @@ const initialState: ShortUrlVisits = {
|
|||
export default buildReducer<ShortUrlVisits, ShortUrlVisitsCombinedAction>({
|
||||
[`${REDUCER_PREFIX}/getShortUrlVisits/pending`]: () => ({ ...initialState, loading: true }),
|
||||
[`${REDUCER_PREFIX}/getShortUrlVisits/rejected`]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
|
||||
[`${REDUCER_PREFIX}/getShortUrlVisits/fulfilled`]: (state, { visits, query, shortCode, domain }) => ({
|
||||
[`${REDUCER_PREFIX}/getShortUrlVisits/fulfilled`]: (state, { payload }: ShortUrlVisitsAction) => ({
|
||||
...state,
|
||||
visits,
|
||||
shortCode,
|
||||
domain,
|
||||
query,
|
||||
...payload,
|
||||
loading: false,
|
||||
loadingLarge: false,
|
||||
error: false,
|
||||
|
@ -96,7 +95,7 @@ export const getShortUrlVisits = (buildShlinkApiClient: ShlinkApiClientBuilder)
|
|||
async (params) => shlinkGetShortUrlVisits(shortCode, { ...params, domain: query.domain }),
|
||||
);
|
||||
const shouldCancel = () => getState().shortUrlVisits.cancelLoad;
|
||||
const extraFinishActionData: Partial<ShortUrlVisitsAction> = { shortCode, query, domain: query.domain };
|
||||
const extraFinishActionData: Partial<VisitsLoaded<ShortUrlIdentifier>> = { shortCode, query, domain: query.domain };
|
||||
const prefix = `${REDUCER_PREFIX}/getShortUrlVisits`;
|
||||
|
||||
return getVisitsWithLoader(visitsLoader, lastVisitLoader, extraFinishActionData, prefix, dispatch, shouldCancel);
|
||||
|
|
|
@ -1,15 +1,20 @@
|
|||
import { createAction } from '@reduxjs/toolkit';
|
||||
import { Action, Dispatch } from 'redux';
|
||||
import { Visit } from '../types';
|
||||
import { Dispatch } from 'redux';
|
||||
import { 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 { createNewVisits, CreateVisitsAction } from './visitCreation';
|
||||
import { VisitsFallbackIntervalAction, VisitsInfo, VisitsLoadProgressChangedAction } from './types';
|
||||
import {
|
||||
LoadVisits,
|
||||
VisitsFallbackIntervalAction,
|
||||
VisitsInfo,
|
||||
VisitsLoaded,
|
||||
VisitsLoadedAction,
|
||||
VisitsLoadProgressChangedAction,
|
||||
} from './types';
|
||||
|
||||
const REDUCER_PREFIX = 'shlink/tagVisits';
|
||||
export const GET_TAG_VISITS_START = `${REDUCER_PREFIX}/getTagVisits/pending`;
|
||||
|
@ -24,12 +29,12 @@ export interface TagVisits extends VisitsInfo {
|
|||
tag: string;
|
||||
}
|
||||
|
||||
export interface TagVisitsAction extends Action<string> {
|
||||
visits: Visit[];
|
||||
export interface LoadTagVisits extends LoadVisits {
|
||||
tag: string;
|
||||
query?: ShlinkVisitsParams;
|
||||
}
|
||||
|
||||
export type TagVisitsAction = VisitsLoadedAction<{ tag: string }>;
|
||||
|
||||
type TagsVisitsCombinedAction = TagVisitsAction
|
||||
& VisitsLoadProgressChangedAction
|
||||
& VisitsFallbackIntervalAction
|
||||
|
@ -49,8 +54,8 @@ const initialState: TagVisits = {
|
|||
export default buildReducer<TagVisits, TagsVisitsCombinedAction>({
|
||||
[`${REDUCER_PREFIX}/getTagVisits/pending`]: () => ({ ...initialState, loading: true }),
|
||||
[`${REDUCER_PREFIX}/getTagVisits/rejected`]: (_, { errorData }) => ({ ...initialState, error: true, errorData }),
|
||||
[`${REDUCER_PREFIX}/getTagVisits/fulfilled`]: (state, { visits, tag, query }) => (
|
||||
{ ...state, visits, tag, query, loading: false, loadingLarge: false, error: false }
|
||||
[`${REDUCER_PREFIX}/getTagVisits/fulfilled`]: (state, { payload }: TagVisitsAction) => (
|
||||
{ ...state, ...payload, loading: false, loadingLarge: false, error: false }
|
||||
),
|
||||
[`${REDUCER_PREFIX}/getTagVisits/large`]: (state) => ({ ...state, loadingLarge: true }),
|
||||
[`${REDUCER_PREFIX}/getTagVisits/cancel`]: (state) => ({ ...state, cancelLoad: true }),
|
||||
|
@ -70,9 +75,7 @@ export default buildReducer<TagVisits, TagsVisitsCombinedAction>({
|
|||
}, initialState);
|
||||
|
||||
export const getTagVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => (
|
||||
tag: string,
|
||||
query: ShlinkVisitsParams = {},
|
||||
doIntervalFallback = false,
|
||||
{ tag, query = {}, doIntervalFallback = false }: LoadTagVisits,
|
||||
) => async (dispatch: Dispatch, getState: GetState) => {
|
||||
const { getTagVisits: getVisits } = buildShlinkApiClient(getState);
|
||||
const visitsLoader = async (page: number, itemsPerPage: number) => getVisits(
|
||||
|
@ -81,7 +84,7 @@ export const getTagVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => (
|
|||
);
|
||||
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, async (params) => getVisits(tag, params));
|
||||
const shouldCancel = () => getState().tagVisits.cancelLoad;
|
||||
const extraFinishActionData: Partial<TagVisitsAction> = { tag, query };
|
||||
const extraFinishActionData: Partial<VisitsLoaded<{ tag: string }>> = { tag, query };
|
||||
const prefix = `${REDUCER_PREFIX}/getTagVisits`;
|
||||
|
||||
return getVisitsWithLoader(visitsLoader, lastVisitLoader, extraFinishActionData, prefix, dispatch, shouldCancel);
|
||||
|
|
|
@ -21,6 +21,13 @@ export interface LoadVisits {
|
|||
doIntervalFallback?: boolean;
|
||||
}
|
||||
|
||||
export type VisitsLoaded<T = {}> = T & {
|
||||
visits: Visit[];
|
||||
query?: ShlinkVisitsParams;
|
||||
};
|
||||
|
||||
export type VisitsLoadedAction<T = {}> = PayloadAction<VisitsLoaded<T>>;
|
||||
|
||||
export type VisitsLoadProgressChangedAction = PayloadAction<number>;
|
||||
|
||||
export type VisitsFallbackIntervalAction = PayloadAction<DateInterval>;
|
||||
|
|
|
@ -1,22 +1,9 @@
|
|||
import { Action } from 'redux';
|
||||
import { buildActionCreator, buildReducer } from '../../../src/utils/helpers/redux';
|
||||
import { buildReducer } from '../../../src/utils/helpers/redux';
|
||||
|
||||
describe('redux', () => {
|
||||
beforeEach(jest.clearAllMocks);
|
||||
|
||||
describe('buildActionCreator', () => {
|
||||
it.each([
|
||||
['foo', { type: 'foo' }],
|
||||
['bar', { type: 'bar' }],
|
||||
['something', { type: 'something' }],
|
||||
])('returns an action creator', (type, expected) => {
|
||||
const actionCreator = buildActionCreator(type);
|
||||
|
||||
expect(actionCreator).toBeInstanceOf(Function);
|
||||
expect(actionCreator()).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildReducer', () => {
|
||||
const fooActionHandler = jest.fn(() => 'foo result');
|
||||
const barActionHandler = jest.fn(() => 'bar result');
|
||||
|
|
|
@ -61,10 +61,10 @@ describe('domainVisitsReducer', () => {
|
|||
|
||||
it('return visits on GET_DOMAIN_VISITS', () => {
|
||||
const actionVisits = [{}, {}];
|
||||
const state = reducer(
|
||||
buildState({ loading: true, error: false }),
|
||||
{ type: GET_DOMAIN_VISITS, visits: actionVisits } as any,
|
||||
);
|
||||
const state = reducer(buildState({ loading: true, error: false }), {
|
||||
type: GET_DOMAIN_VISITS,
|
||||
payload: { visits: actionVisits },
|
||||
} as any);
|
||||
const { loading, error, visits } = state;
|
||||
|
||||
expect(loading).toEqual(false);
|
||||
|
@ -201,7 +201,10 @@ describe('domainVisitsReducer', () => {
|
|||
|
||||
expect(dispatchMock).toHaveBeenCalledTimes(2);
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_DOMAIN_VISITS_START });
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_DOMAIN_VISITS, visits, domain, query: query ?? {} });
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(2, {
|
||||
type: GET_DOMAIN_VISITS,
|
||||
payload: { visits, domain, query: query ?? {} },
|
||||
});
|
||||
expect(shlinkApiClient.getDomainVisits).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -59,10 +59,10 @@ describe('nonOrphanVisitsReducer', () => {
|
|||
|
||||
it('return visits on GET_NON_ORPHAN_VISITS', () => {
|
||||
const actionVisits = [{}, {}];
|
||||
const state = reducer(
|
||||
buildState({ loading: true, error: false }),
|
||||
{ type: GET_NON_ORPHAN_VISITS, visits: actionVisits } as any,
|
||||
);
|
||||
const state = reducer(buildState({ loading: true, error: false }), {
|
||||
type: GET_NON_ORPHAN_VISITS,
|
||||
payload: { visits: actionVisits },
|
||||
} as any);
|
||||
const { loading, error, visits } = state;
|
||||
|
||||
expect(loading).toEqual(false);
|
||||
|
@ -173,7 +173,10 @@ describe('nonOrphanVisitsReducer', () => {
|
|||
|
||||
expect(dispatchMock).toHaveBeenCalledTimes(2);
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_NON_ORPHAN_VISITS_START });
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_NON_ORPHAN_VISITS, visits, query: query ?? {} });
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(2, {
|
||||
type: GET_NON_ORPHAN_VISITS,
|
||||
payload: { visits, query: query ?? {} },
|
||||
});
|
||||
expect(ShlinkApiClient.getNonOrphanVisits).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -59,10 +59,10 @@ describe('orphanVisitsReducer', () => {
|
|||
|
||||
it('return visits on GET_ORPHAN_VISITS', () => {
|
||||
const actionVisits = [{}, {}];
|
||||
const state = reducer(
|
||||
buildState({ loading: true, error: false }),
|
||||
{ type: GET_ORPHAN_VISITS, visits: actionVisits } as any,
|
||||
);
|
||||
const state = reducer(buildState({ loading: true, error: false }), {
|
||||
type: GET_ORPHAN_VISITS,
|
||||
payload: { visits: actionVisits },
|
||||
} as any);
|
||||
const { loading, error, visits } = state;
|
||||
|
||||
expect(loading).toEqual(false);
|
||||
|
@ -173,7 +173,10 @@ describe('orphanVisitsReducer', () => {
|
|||
|
||||
expect(dispatchMock).toHaveBeenCalledTimes(2);
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_ORPHAN_VISITS_START });
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_ORPHAN_VISITS, visits, query: query ?? {} });
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(2, {
|
||||
type: GET_ORPHAN_VISITS,
|
||||
payload: { visits, query: query ?? {} },
|
||||
});
|
||||
expect(ShlinkApiClient.getOrphanVisits).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
|
|
@ -59,10 +59,10 @@ describe('shortUrlVisitsReducer', () => {
|
|||
|
||||
it('return visits on GET_SHORT_URL_VISITS', () => {
|
||||
const actionVisits = [{}, {}];
|
||||
const state = reducer(
|
||||
buildState({ loading: true, error: false }),
|
||||
{ type: GET_SHORT_URL_VISITS, visits: actionVisits } as any,
|
||||
);
|
||||
const state = reducer(buildState({ loading: true, error: false }), {
|
||||
type: GET_SHORT_URL_VISITS,
|
||||
payload: { visits: actionVisits },
|
||||
} as any);
|
||||
const { loading, error, visits } = state;
|
||||
|
||||
expect(loading).toEqual(false);
|
||||
|
@ -195,10 +195,10 @@ describe('shortUrlVisitsReducer', () => {
|
|||
|
||||
expect(dispatchMock).toHaveBeenCalledTimes(2);
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_SHORT_URL_VISITS_START });
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
{ type: GET_SHORT_URL_VISITS, visits, shortCode, domain, query: query ?? {} },
|
||||
);
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(2, {
|
||||
type: GET_SHORT_URL_VISITS,
|
||||
payload: { visits, shortCode, domain, query: query ?? {} },
|
||||
});
|
||||
expect(ShlinkApiClient.getShortUrlVisits).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
@ -218,7 +218,9 @@ describe('shortUrlVisitsReducer', () => {
|
|||
|
||||
expect(ShlinkApiClient.getShortUrlVisits).toHaveBeenCalledTimes(expectedRequests);
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(3, expect.objectContaining({
|
||||
visits: [...visitsMocks, ...visitsMocks, ...visitsMocks],
|
||||
payload: expect.objectContaining({
|
||||
visits: [...visitsMocks, ...visitsMocks, ...visitsMocks],
|
||||
}),
|
||||
}));
|
||||
});
|
||||
|
||||
|
|
|
@ -59,10 +59,10 @@ describe('tagVisitsReducer', () => {
|
|||
|
||||
it('return visits on GET_TAG_VISITS', () => {
|
||||
const actionVisits = [{}, {}];
|
||||
const state = reducer(
|
||||
buildState({ loading: true, error: false }),
|
||||
{ type: GET_TAG_VISITS, visits: actionVisits } as any,
|
||||
);
|
||||
const state = reducer(buildState({ loading: true, error: false }), {
|
||||
type: GET_TAG_VISITS,
|
||||
payload: { visits: actionVisits },
|
||||
} as any);
|
||||
const { loading, error, visits } = state;
|
||||
|
||||
expect(loading).toEqual(false);
|
||||
|
@ -165,7 +165,7 @@ describe('tagVisitsReducer', () => {
|
|||
it('dispatches start and error when promise is rejected', async () => {
|
||||
const shlinkApiClient = buildApiClientMock(Promise.reject(new Error()));
|
||||
|
||||
await getTagVisits(() => shlinkApiClient)('foo')(dispatchMock, getState);
|
||||
await getTagVisits(() => shlinkApiClient)({ tag })(dispatchMock, getState);
|
||||
|
||||
expect(dispatchMock).toHaveBeenCalledTimes(2);
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_TAG_VISITS_START });
|
||||
|
@ -187,11 +187,14 @@ describe('tagVisitsReducer', () => {
|
|||
},
|
||||
}));
|
||||
|
||||
await getTagVisits(() => shlinkApiClient)(tag, query)(dispatchMock, getState);
|
||||
await getTagVisits(() => shlinkApiClient)({ tag, query })(dispatchMock, getState);
|
||||
|
||||
expect(dispatchMock).toHaveBeenCalledTimes(2);
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_TAG_VISITS_START });
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(2, { type: GET_TAG_VISITS, visits, tag, query: query ?? {} });
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(2, {
|
||||
type: GET_TAG_VISITS,
|
||||
payload: { visits, tag, query: query ?? {} },
|
||||
});
|
||||
expect(shlinkApiClient.getTagVisits).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
|
@ -219,7 +222,7 @@ describe('tagVisitsReducer', () => {
|
|||
.mockResolvedValueOnce(buildVisitsResult(lastVisits));
|
||||
const ShlinkApiClient = Mock.of<ShlinkApiClient>({ getTagVisits: getShlinkTagVisits });
|
||||
|
||||
await getTagVisits(() => ShlinkApiClient)(tag, {}, true)(dispatchMock, getState);
|
||||
await getTagVisits(() => ShlinkApiClient)({ tag, doIntervalFallback: true })(dispatchMock, getState);
|
||||
|
||||
expect(dispatchMock).toHaveBeenCalledTimes(2);
|
||||
expect(dispatchMock).toHaveBeenNthCalledWith(1, { type: GET_TAG_VISITS_START });
|
||||
|
|
Loading…
Reference in a new issue