Updated to airbnb coding styles

This commit is contained in:
Alejandro Celaya
2022-03-26 12:17:42 +01:00
parent 4e9b19afd1
commit a2df486280
239 changed files with 2210 additions and 3549 deletions

View File

@@ -39,4 +39,4 @@ export const NonOrphanVisits = ({ exportVisits }: ReportExporter) => boundToMerc
<NonOrphanVisitsHeader nonOrphanVisits={nonOrphanVisits} goBack={goBack} />
</VisitsStats>
);
}, () => [ Topics.visits ]);
}, () => [Topics.visits]);

View File

@@ -44,4 +44,4 @@ export const OrphanVisits = ({ exportVisits }: ReportExporter) => boundToMercure
<OrphanVisitsHeader orphanVisits={orphanVisits} goBack={goBack} />
</VisitsStats>
);
}, () => [ Topics.orphanVisits ]);
}, () => [Topics.orphanVisits]);

View File

@@ -59,6 +59,6 @@ const ShortUrlVisits = ({ exportVisits }: ReportExporter) => boundToMercureHub((
<ShortUrlVisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={goBack} />
</VisitsStats>
);
}, (_, params) => [ Topics.shortUrlVisits(params.shortCode) ]);
}, (_, params) => [Topics.shortUrlVisits(params.shortCode)]);
export default ShortUrlVisits;

View File

@@ -19,7 +19,7 @@ const ShortUrlVisitsHeader = ({ shortUrlDetail, shortUrlVisits, goBack }: ShortU
const longLink = shortUrl?.longUrl ?? '';
const title = shortUrl?.title;
const renderDate = () => !shortUrl ? <small>Loading...</small> : (
const renderDate = () => (!shortUrl ? <small>Loading...</small> : (
<span>
<b id="created" className="short-url-visits-header__created-at">
<Time date={shortUrl.dateCreated} relative />
@@ -28,7 +28,7 @@ const ShortUrlVisitsHeader = ({ shortUrlDetail, shortUrlVisits, goBack }: ShortU
<Time date={shortUrl.dateCreated} />
</UncontrolledTooltip>
</span>
);
));
const visitsStatsTitle = <>Visits for <ExternalLink href={shortLink} /></>;
return (

View File

@@ -43,6 +43,6 @@ const TagVisits = (colorGenerator: ColorGenerator, { exportVisits }: ReportExpor
<TagVisitsHeader tagVisits={tagVisits} goBack={goBack} colorGenerator={colorGenerator} />
</VisitsStats>
);
}, () => [ Topics.visits ]);
}, () => [Topics.visits]);
export default TagVisits;

View File

@@ -67,13 +67,13 @@ const VisitsStats: FC<VisitsStatsProps> = ({
isOrphanVisits = false,
}) => {
const { visits, loading, loadingLarge, error, errorData, progress, fallbackInterval } = visitsInfo;
const [ initialInterval, setInitialInterval ] = useState<DateInterval>(
const [initialInterval, setInitialInterval] = useState<DateInterval>(
fallbackInterval ?? settings.visits?.defaultInterval ?? 'last30Days',
);
const [ dateRange, setDateRange ] = useState<DateRange>(intervalToDateRange(initialInterval));
const [ highlightedVisits, setHighlightedVisits ] = useState<NormalizedVisit[]>([]);
const [ highlightedLabel, setHighlightedLabel ] = useState<string | undefined>();
const [ visitsFilter, setVisitsFilter ] = useState<VisitsFilter>({});
const [dateRange, setDateRange] = useState<DateRange>(intervalToDateRange(initialInterval));
const [highlightedVisits, setHighlightedVisits] = useState<NormalizedVisit[]>([]);
const [highlightedLabel, setHighlightedLabel] = useState<string | undefined>();
const [visitsFilter, setVisitsFilter] = useState<VisitsFilter>({});
const botsSupported = supportsBotVisits(selectedServer);
const isFirstLoad = useRef(true);
@@ -82,10 +82,10 @@ const VisitsStats: FC<VisitsStatsProps> = ({
return !subPath ? `${query}` : `${subPath}${query}`;
};
const normalizedVisits = useMemo(() => normalizeVisits(visits), [ visits ]);
const normalizedVisits = useMemo(() => normalizeVisits(visits), [visits]);
const { os, browsers, referrers, countries, cities, citiesForMap, visitedUrls } = useMemo(
() => processStatsFromVisits(normalizedVisits),
[ normalizedVisits ],
[normalizedVisits],
);
const mapLocations = values(citiesForMap);
@@ -111,10 +111,10 @@ const VisitsStats: FC<VisitsStatsProps> = ({
useEffect(() => {
getVisits({ dateRange, filter: visitsFilter }, isFirstLoad.current);
isFirstLoad.current = false;
}, [ dateRange, visitsFilter ]);
}, [dateRange, visitsFilter]);
useEffect(() => {
fallbackInterval && setInitialInterval(fallbackInterval);
}, [ fallbackInterval ]);
}, [fallbackInterval]);
const renderVisitsContent = () => {
if (loadingLarge) {
@@ -235,10 +235,9 @@ const VisitsStats: FC<VisitsStatsProps> = ({
stats={cities}
highlightedStats={highlightedVisitsToStats(highlightedVisits, 'city')}
highlightedLabel={highlightedLabel}
extraHeaderContent={(activeCities: string[]) =>
mapLocations.length > 0 &&
extraHeaderContent={(activeCities: string[]) => mapLocations.length > 0 && (
<OpenMapModalBtn modalTitle="Cities" locations={mapLocations} activeCities={activeCities} />
}
)}
sortingItems={{
name: 'City name',
amount: 'Visits amount',

View File

@@ -37,7 +37,7 @@ const searchVisits = (searchTerm: string, visits: NormalizedVisit[]) =>
visits.filter((visit) => visitMatchesSearch(visit, searchTerm));
const sortVisits = (order: VisitsOrder, visits: NormalizedVisit[]) => sortList<NormalizedVisit>(visits, order as any);
const calculateVisits = (allVisits: NormalizedVisit[], searchTerm: string | undefined, order: VisitsOrder) => {
const filteredVisits = searchTerm ? searchVisits(searchTerm, allVisits) : [ ...allVisits ];
const filteredVisits = searchTerm ? searchVisits(searchTerm, allVisits) : [...allVisits];
const sortedVisits = sortVisits(order, filteredVisits);
const total = sortedVisits.length;
const visitsGroups = splitEvery(PAGE_SIZE, sortedVisits);
@@ -56,12 +56,12 @@ const VisitsTable = ({
const headerCellsClass = 'visits-table__header-cell visits-table__sticky';
const matchMobile = () => matchMedia('(max-width: 767px)').matches;
const [ isMobileDevice, setIsMobileDevice ] = useState(matchMobile());
const [ searchTerm, setSearchTerm ] = useState<string | undefined>(undefined);
const [ order, setOrder ] = useState<VisitsOrder>({});
const resultSet = useMemo(() => calculateVisits(visits, searchTerm, order), [ searchTerm, order ]);
const [isMobileDevice, setIsMobileDevice] = useState(matchMobile());
const [searchTerm, setSearchTerm] = useState<string | undefined>(undefined);
const [order, setOrder] = useState<VisitsOrder>({});
const resultSet = useMemo(() => calculateVisits(visits, searchTerm, order), [searchTerm, order]);
const isFirstLoad = useRef(true);
const [ page, setPage ] = useState(1);
const [page, setPage] = useState(1);
const end = page * PAGE_SIZE;
const start = end - PAGE_SIZE;
const supportsBots = supportsBotVisits(selectedServer);
@@ -84,7 +84,7 @@ const VisitsTable = ({
!isFirstLoad.current && setSelectedVisits([]);
isFirstLoad.current = false;
}, [ searchTerm ]);
}, [searchTerm]);
return (
<div className="table-responsive-md">
@@ -159,7 +159,7 @@ const VisitsTable = ({
style={{ cursor: 'pointer' }}
className={classNames({ 'table-active': isSelected })}
onClick={() => setSelectedVisits(
isSelected ? selectedVisits.filter((v) => v !== visit) : [ ...selectedVisits, visit ],
isSelected ? selectedVisits.filter((v) => v !== visit) : [...selectedVisits, visit],
)}
>
<td className="text-center">

View File

@@ -37,7 +37,7 @@ const generateChartData = (labels: string[], data: number[]): ChartData => ({
});
export const DoughnutChart: FC<DoughnutChartProps> = memo(({ stats }) => {
const [ chartRef, setChartRef ] = useState<Chart | undefined>(); // Cannot use useRef here
const [chartRef, setChartRef] = useState<Chart | undefined>(); // Cannot use useRef here
const labels = keys(stats);
const data = values(stats);

View File

@@ -16,9 +16,9 @@ export interface HorizontalBarChartProps {
onClick?: (label: string) => void;
}
const dropLabelIfHidden = (label: string) => label.startsWith('hidden') ? '' : label;
const dropLabelIfHidden = (label: string) => (label.startsWith('hidden') ? '' : label);
const statsAreDefined = (stats: Stats | undefined): stats is Stats => !!stats && Object.keys(stats).length > 0;
const determineHeight = (labels: string[]): number | undefined => labels.length > 20 ? labels.length * 10 : undefined;
const determineHeight = (labels: string[]): number | undefined => (labels.length > 20 ? labels.length * 10 : undefined);
const generateChartDatasets = (
data: number[],
@@ -34,7 +34,7 @@ const generateChartDatasets = (
};
if (highlightedData.every((value) => value === 0)) {
return [ mainDataset ];
return [mainDataset];
}
const highlightedDataset: ChartDataset = {
@@ -45,7 +45,7 @@ const generateChartDatasets = (
borderWidth: 2,
};
return [ mainDataset, highlightedDataset ];
return [mainDataset, highlightedDataset];
};
const generateChartData = (
labels: string[],
@@ -58,7 +58,7 @@ const generateChartData = (
});
type ClickedCharts = [{ index: number }] | [];
const chartElementAtEvent = (labels: string[], onClick?: (label: string) => void) => ([ chart ]: ClickedCharts) => {
const chartElementAtEvent = (labels: string[], onClick?: (label: string) => void) => ([chart]: ClickedCharts) => {
if (!onClick || !chart) {
return;
}

View File

@@ -79,9 +79,9 @@ const determineInitialStep = (oldestVisitDate: string): Step => {
const now = new Date();
const oldestDate = parseISO(oldestVisitDate);
const matcher = cond<never, Step | undefined>([
[ () => differenceInDays(now, oldestDate) <= 2, always<Step>('hourly') ], // Less than 2 days
[ () => differenceInMonths(now, oldestDate) <= 1, always<Step>('daily') ], // Between 2 days and 1 month
[ () => differenceInMonths(now, oldestDate) <= 6, always<Step>('weekly') ], // Between 1 and 6 months
[() => differenceInDays(now, oldestDate) <= 2, always<Step>('hourly')], // Less than 2 days
[() => differenceInMonths(now, oldestDate) <= 1, always<Step>('daily')], // Between 2 days and 1 month
[() => differenceInMonths(now, oldestDate) <= 6, always<Step>('weekly')], // Between 1 and 6 months
]);
return matcher() ?? 'monthly';
@@ -126,12 +126,12 @@ const generateLabelsAndGroupedVisits = (
skipNoElements: boolean,
): [string[], number[]] => {
if (skipNoElements) {
return [ Object.keys(groupedVisitsWithGaps), Object.values(groupedVisitsWithGaps) ];
return [Object.keys(groupedVisitsWithGaps), Object.values(groupedVisitsWithGaps)];
}
const labels = generateLabels(step, visits);
return [ labels, fillTheGaps(groupedVisitsWithGaps, labels) ];
return [labels, fillTheGaps(groupedVisitsWithGaps, labels)];
};
const generateDataset = (data: number[], label: string, color: string): ChartDataset => ({
@@ -149,7 +149,7 @@ const chartElementAtEvent = (
labels: string[],
datasetsByPoint: Record<string, NormalizedVisit[]>,
setSelectedVisits?: (visits: NormalizedVisit[]) => void,
) => ([ chart ]: [{ index: number }]) => {
) => ([chart]: [{ index: number }]) => {
if (!setSelectedVisits || !chart) {
return;
}
@@ -160,7 +160,7 @@ const chartElementAtEvent = (
setSelectedVisits([]);
selectedLabel = null;
} else {
setSelectedVisits(labels[index] && datasetsByPoint[labels[index]] || []);
setSelectedVisits(labels[index] ? datasetsByPoint[labels[index]] : []);
selectedLabel = labels[index] ?? null;
}
};
@@ -168,31 +168,31 @@ const chartElementAtEvent = (
const LineChartCard = (
{ title, visits, highlightedVisits, highlightedLabel = 'Selected', setSelectedVisits }: LineChartCardProps,
) => {
const [ step, setStep ] = useState<Step>(
const [step, setStep] = useState<Step>(
visits.length > 0 ? determineInitialStep(visits[visits.length - 1].date) : 'monthly',
);
const [ skipNoVisits, toggleSkipNoVisits ] = useToggle(true);
const [skipNoVisits, toggleSkipNoVisits] = useToggle(true);
const datasetsByPoint = useMemo(() => visitsToDatasetGroups(step, visits), [ step, visits ]);
const groupedVisitsWithGaps = useMemo(() => groupVisitsByStep(step, reverse(visits)), [ step, visits ]);
const [ labels, groupedVisits ] = useMemo(
const datasetsByPoint = useMemo(() => visitsToDatasetGroups(step, visits), [step, visits]);
const groupedVisitsWithGaps = useMemo(() => groupVisitsByStep(step, reverse(visits)), [step, visits]);
const [labels, groupedVisits] = useMemo(
() => generateLabelsAndGroupedVisits(visits, groupedVisitsWithGaps, step, skipNoVisits),
[ visits, step, skipNoVisits ],
[visits, step, skipNoVisits],
);
const groupedHighlighted = useMemo(
() => fillTheGaps(groupVisitsByStep(step, reverse(highlightedVisits)), labels),
[ highlightedVisits, step, labels ],
[highlightedVisits, step, labels],
);
const generateChartDatasets = (): ChartDataset[] => {
const mainDataset = generateDataset(groupedVisits, 'Visits', MAIN_COLOR);
if (highlightedVisits.length === 0) {
return [ mainDataset ];
return [mainDataset];
}
const highlightedDataset = generateDataset(groupedHighlighted, highlightedLabel, HIGHLIGHTED_COLOR);
return [ mainDataset, highlightedDataset ];
return [mainDataset, highlightedDataset];
};
const generateChartData = (): ChartData => ({ labels, datasets: generateChartDatasets() });
@@ -238,7 +238,7 @@ const LineChartCard = (
Group by
</DropdownToggle>
<DropdownMenu end>
{Object.entries(STEPS_MAP).map(([ value, menuText ]) => (
{Object.entries(STEPS_MAP).map(([value, menuText]) => (
<DropdownItem key={value} active={step === value} onClick={() => setStep(value as Step)}>
{menuText}
</DropdownItem>

View File

@@ -17,9 +17,9 @@ interface SortableBarChartCardProps extends Omit<HorizontalBarChartProps, 'max'>
extraHeaderContent?: Function;
}
const toLowerIfString = (value: any) => type(value) === 'String' ? toLower(value) : value; // eslint-disable-line @typescript-eslint/no-unsafe-return
const pickKeyFromPair = ([ key ]: StatsRow) => key;
const pickValueFromPair = ([ , value ]: StatsRow) => value;
const toLowerIfString = (value: any) => (type(value) === 'String' ? toLower(value) : value);
const pickKeyFromPair = ([key]: StatsRow) => key;
const pickValueFromPair = ([, value]: StatsRow) => value;
export const SortableBarChartCard: FC<SortableBarChartCardProps> = ({
stats,
@@ -30,15 +30,15 @@ export const SortableBarChartCard: FC<SortableBarChartCardProps> = ({
withPagination = true,
...rest
}) => {
const [ order, setOrder ] = useState<Order<string>>({});
const [ currentPage, setCurrentPage ] = useState(1);
const [ itemsPerPage, setItemsPerPage ] = useState(50);
const [order, setOrder] = useState<Order<string>>({});
const [currentPage, setCurrentPage] = useState(1);
const [itemsPerPage, setItemsPerPage] = useState(50);
const getSortedPairsForStats = (stats: Stats, sortingItems: Record<string, string>) => {
const pairs = toPairs(stats);
const getSortedPairsForStats = (statsToSort: Stats, sorting: Record<string, string>) => {
const pairs = toPairs(statsToSort);
const sortedPairs = !order.field ? pairs : sortBy(
pipe<StatsRow, string | number, string | number>(
order.field === Object.keys(sortingItems)[0] ? pickKeyFromPair : pickValueFromPair,
order.field === Object.keys(sorting)[0] ? pickKeyFromPair : pickValueFromPair,
toLowerIfString,
),
pairs,
@@ -56,16 +56,16 @@ export const SortableBarChartCard: FC<SortableBarChartCardProps> = ({
const firstPageLength = pages[0].length;
// Using the "hidden" key, the chart will just replace the label by an empty string
return [ ...page, ...rangeOf(firstPageLength - page.length, (i): StatsRow => [ `hidden_${i}`, 0 ]) ];
return [...page, ...rangeOf(firstPageLength - page.length, (i): StatsRow => [`hidden_${i}`, 0])];
};
const renderPagination = (pagesCount: number) =>
<SimplePaginator currentPage={currentPage} pagesCount={pagesCount} setCurrentPage={setCurrentPage} />;
const determineStats = (stats: Stats, highlightedStats: Stats | undefined, sortingItems: Record<string, string>) => {
const sortedPairs = getSortedPairsForStats(stats, sortingItems);
const determineStats = (statsToSort: Stats, sorting: Record<string, string>, theHighlightedStats?: Stats) => {
const sortedPairs = getSortedPairsForStats(statsToSort, sorting);
const sortedKeys = sortedPairs.map(pickKeyFromPair);
// The highlighted stats have to be ordered based on the regular stats, not on its own values
const sortedHighlightedPairs = highlightedStats && toPairs(
{ ...zipObj(sortedKeys, sortedKeys.map(() => 0)), ...highlightedStats },
const sortedHighlightedPairs = theHighlightedStats && toPairs(
{ ...zipObj(sortedKeys, sortedKeys.map(() => 0)), ...theHighlightedStats },
);
if (sortedPairs.length <= itemsPerPage) {
@@ -88,8 +88,8 @@ export const SortableBarChartCard: FC<SortableBarChartCardProps> = ({
const { currentPageStats, currentPageHighlightedStats, pagination, max } = determineStats(
stats,
highlightedStats && Object.keys(highlightedStats).length > 0 ? highlightedStats : undefined,
sortingItems,
highlightedStats && Object.keys(highlightedStats).length > 0 ? highlightedStats : undefined,
);
const activeCities = Object.keys(currentPageStats);
const computeTitle = () => (
@@ -111,10 +111,10 @@ export const SortableBarChartCard: FC<SortableBarChartCardProps> = ({
<div className="float-end">
<PaginationDropdown
toggleClassName="btn-sm p-0 me-3"
ranges={[ 50, 100, 200, 500 ]}
ranges={[50, 100, 200, 500]}
value={itemsPerPage}
setValue={(itemsPerPage) => {
setItemsPerPage(itemsPerPage);
setValue={(value) => {
setItemsPerPage(value);
setCurrentPage(1);
}}
/>

View File

@@ -1,7 +1,7 @@
import { useRef, useState } from 'react';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faMapMarkedAlt as mapIcon } from '@fortawesome/free-solid-svg-icons';
import { Dropdown, DropdownItem, DropdownMenu, UncontrolledTooltip } from 'reactstrap';
import { Button, Dropdown, DropdownItem, DropdownMenu, UncontrolledTooltip } from 'reactstrap';
import { useToggle } from '../../utils/helpers/hooks';
import { CityStats } from '../types';
import MapModal from './MapModal';
@@ -14,9 +14,9 @@ interface OpenMapModalBtnProps {
}
const OpenMapModalBtn = ({ modalTitle, activeCities, locations = [] }: OpenMapModalBtnProps) => {
const [ mapIsOpened, , openMap, closeMap ] = useToggle();
const [ dropdownIsOpened, toggleDropdown, openDropdown ] = useToggle();
const [ locationsToShow, setLocationsToShow ] = useState<CityStats[]>([]);
const [mapIsOpened, , openMap, closeMap] = useToggle();
const [dropdownIsOpened, toggleDropdown, openDropdown] = useToggle();
const [locationsToShow, setLocationsToShow] = useState<CityStats[]>([]);
const buttonRef = useRef<HTMLElement>();
const filterLocations = (cities: CityStats[]) => cities.filter(({ cityName }) => activeCities.includes(cityName));
@@ -37,9 +37,9 @@ const OpenMapModalBtn = ({ modalTitle, activeCities, locations = [] }: OpenMapMo
return (
<>
<button className="btn btn-link open-map-modal-btn__btn" ref={buttonRef as any} onClick={onClick}>
<Button color="link" className="open-map-modal-btn__btn" ref={buttonRef as any} onClick={onClick}>
<FontAwesomeIcon icon={mapIcon} />
</button>
</Button>
<UncontrolledTooltip placement="left" target={(() => buttonRef.current) as any}>Show in map</UncontrolledTooltip>
<Dropdown isOpen={dropdownIsOpened} toggle={toggleDropdown} inNavbar>
<DropdownMenu end>

View File

@@ -11,7 +11,7 @@ const PARALLEL_REQUESTS_COUNT = 4;
const PARALLEL_STARTING_PAGE = 2;
const isLastPage = ({ currentPage, pagesCount }: ShlinkPaginator): boolean => currentPage >= pagesCount;
const calcProgress = (total: number, current: number): number => current * 100 / total;
const calcProgress = (total: number, current: number): number => (current * 100) / total;
type VisitsLoader = (page: number, itemsPerPage: number) => Promise<ShlinkVisits>;
type LastVisitLoader = () => Promise<Visit | undefined>;
@@ -73,7 +73,7 @@ export const getVisitsWithLoader = async <T extends Action<string> & { visits: V
};
try {
const [ visits, lastVisit ] = await Promise.all([ loadVisits(), lastVisitLoader() ]);
const [visits, lastVisit] = await Promise.all([loadVisits(), lastVisitLoader()]);
dispatch(
!visits.length && lastVisit

View File

@@ -14,7 +14,6 @@ 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';
@@ -22,7 +21,6 @@ export const GET_NON_ORPHAN_VISITS_LARGE = 'shlink/orphanVisits/GET_NON_ORPHAN_V
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[];
@@ -59,7 +57,7 @@ export default buildReducer<VisitsInfo, NonOrphanVisitsCombinedAction>({
.filter(({ visit }) => isBetween(visit.date, startDate, endDate))
.map(({ visit }) => visit);
return { ...state, visits: [ ...newVisits, ...visits ] };
return { ...state, visits: [...newVisits, ...visits] };
},
}, initialState);
@@ -67,10 +65,10 @@ export const getNonOrphanVisits = (buildShlinkApiClient: ShlinkApiClientBuilder)
query: ShlinkVisitsParams = {},
doIntervalFallback = false,
) => async (dispatch: Dispatch, getState: GetState) => {
const { getNonOrphanVisits } = buildShlinkApiClient(getState);
const { getNonOrphanVisits: shlinkGetNonOrphanVisits } = buildShlinkApiClient(getState);
const visitsLoader = async (page: number, itemsPerPage: number) =>
getNonOrphanVisits({ ...query, page, itemsPerPage });
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, getNonOrphanVisits);
shlinkGetNonOrphanVisits({ ...query, page, itemsPerPage });
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, shlinkGetNonOrphanVisits);
const shouldCancel = () => getState().orphanVisits.cancelLoad;
const extraFinishActionData: Partial<NonOrphanVisitsAction> = { query };
const actionMap = {

View File

@@ -17,7 +17,6 @@ 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_ORPHAN_VISITS_START = 'shlink/orphanVisits/GET_ORPHAN_VISITS_START';
export const GET_ORPHAN_VISITS_ERROR = 'shlink/orphanVisits/GET_ORPHAN_VISITS_ERROR';
export const GET_ORPHAN_VISITS = 'shlink/orphanVisits/GET_ORPHAN_VISITS';
@@ -25,7 +24,6 @@ export const GET_ORPHAN_VISITS_LARGE = 'shlink/orphanVisits/GET_ORPHAN_VISITS_LA
export const GET_ORPHAN_VISITS_CANCEL = 'shlink/orphanVisits/GET_ORPHAN_VISITS_CANCEL';
export const GET_ORPHAN_VISITS_PROGRESS_CHANGED = 'shlink/orphanVisits/GET_ORPHAN_VISITS_PROGRESS_CHANGED';
export const GET_ORPHAN_VISITS_FALLBACK_TO_INTERVAL = 'shlink/orphanVisits/GET_ORPHAN_VISITS_FALLBACK_TO_INTERVAL';
/* eslint-enable padding-line-between-statements */
export interface OrphanVisitsAction extends Action<string> {
visits: Visit[];
@@ -62,7 +60,7 @@ export default buildReducer<VisitsInfo, OrphanVisitsCombinedAction>({
.filter(({ visit, shortUrl }) => !shortUrl && isBetween(visit.date, startDate, endDate))
.map(({ visit }) => visit);
return { ...state, visits: [ ...newVisits, ...visits ] };
return { ...state, visits: [...newVisits, ...visits] };
},
}, initialState);
@@ -74,14 +72,14 @@ export const getOrphanVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) =>
orphanVisitsType?: OrphanVisitType,
doIntervalFallback = false,
) => async (dispatch: Dispatch, getState: GetState) => {
const { getOrphanVisits } = buildShlinkApiClient(getState);
const visitsLoader = async (page: number, itemsPerPage: number) => getOrphanVisits({ ...query, page, itemsPerPage })
const { getOrphanVisits: getVisits } = buildShlinkApiClient(getState);
const visitsLoader = async (page: number, itemsPerPage: number) => getVisits({ ...query, page, itemsPerPage })
.then((result) => {
const visits = result.data.filter((visit) => isOrphanVisit(visit) && matchesType(visit, orphanVisitsType));
return { ...result, data: visits };
});
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, getOrphanVisits);
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, getVisits);
const shouldCancel = () => getState().orphanVisits.cancelLoad;
const extraFinishActionData: Partial<OrphanVisitsAction> = { query };
const actionMap = {

View File

@@ -11,7 +11,6 @@ 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_SHORT_URL_VISITS_START = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_START';
export const GET_SHORT_URL_VISITS_ERROR = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_ERROR';
export const GET_SHORT_URL_VISITS = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS';
@@ -19,7 +18,6 @@ export const GET_SHORT_URL_VISITS_LARGE = 'shlink/shortUrlVisits/GET_SHORT_URL_V
export const GET_SHORT_URL_VISITS_CANCEL = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_CANCEL';
export const GET_SHORT_URL_VISITS_PROGRESS_CHANGED = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_PROGRESS_CHANGED';
export const GET_SHORT_URL_VISITS_FALLBACK_TO_INTERVAL = 'shlink/shortUrlVisits/GET_SHORT_URL_VISITS_FALLBACK_TO_INTERVAL';
/* eslint-enable padding-line-between-statements */
export interface ShortUrlVisits extends VisitsInfo, ShortUrlIdentifier {}
@@ -71,7 +69,7 @@ export default buildReducer<ShortUrlVisits, ShortUrlVisitsCombinedAction>({
)
.map(({ visit }) => visit);
return newVisits.length === 0 ? state : { ...state, visits: [ ...newVisits, ...visits ] };
return newVisits.length === 0 ? state : { ...state, visits: [...newVisits, ...visits] };
},
}, initialState);
@@ -80,14 +78,14 @@ export const getShortUrlVisits = (buildShlinkApiClient: ShlinkApiClientBuilder)
query: ShlinkVisitsParams = {},
doIntervalFallback = false,
) => async (dispatch: Dispatch, getState: GetState) => {
const { getShortUrlVisits } = buildShlinkApiClient(getState);
const visitsLoader = async (page: number, itemsPerPage: number) => getShortUrlVisits(
const { getShortUrlVisits: shlinkGetShortUrlVisits } = buildShlinkApiClient(getState);
const visitsLoader = async (page: number, itemsPerPage: number) => shlinkGetShortUrlVisits(
shortCode,
{ ...query, page, itemsPerPage },
);
const lastVisitLoader = lastVisitLoaderForLoader(
doIntervalFallback,
async (params) => getShortUrlVisits(shortCode, { ...params, domain: query.domain }),
async (params) => shlinkGetShortUrlVisits(shortCode, { ...params, domain: query.domain }),
);
const shouldCancel = () => getState().shortUrlVisits.cancelLoad;
const extraFinishActionData: Partial<ShortUrlVisitsAction> = { shortCode, query, domain: query.domain };

View File

@@ -9,7 +9,6 @@ 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_TAG_VISITS_START = 'shlink/tagVisits/GET_TAG_VISITS_START';
export const GET_TAG_VISITS_ERROR = 'shlink/tagVisits/GET_TAG_VISITS_ERROR';
export const GET_TAG_VISITS = 'shlink/tagVisits/GET_TAG_VISITS';
@@ -17,7 +16,6 @@ export const GET_TAG_VISITS_LARGE = 'shlink/tagVisits/GET_TAG_VISITS_LARGE';
export const GET_TAG_VISITS_CANCEL = 'shlink/tagVisits/GET_TAG_VISITS_CANCEL';
export const GET_TAG_VISITS_PROGRESS_CHANGED = 'shlink/tagVisits/GET_TAG_VISITS_PROGRESS_CHANGED';
export const GET_TAG_VISITS_FALLBACK_TO_INTERVAL = 'shlink/tagVisits/GET_TAG_VISITS_FALLBACK_TO_INTERVAL';
/* eslint-enable padding-line-between-statements */
export interface TagVisits extends VisitsInfo {
tag: string;
@@ -60,7 +58,7 @@ export default buildReducer<TagVisits, TagsVisitsCombinedAction>({
.filter(({ shortUrl, visit }) => shortUrl?.tags.includes(tag) && isBetween(visit.date, startDate, endDate))
.map(({ visit }) => visit);
return { ...state, visits: [ ...newVisits, ...visits ] };
return { ...state, visits: [...newVisits, ...visits] };
},
}, initialState);
@@ -69,12 +67,12 @@ export const getTagVisits = (buildShlinkApiClient: ShlinkApiClientBuilder) => (
query: ShlinkVisitsParams = {},
doIntervalFallback = false,
) => async (dispatch: Dispatch, getState: GetState) => {
const { getTagVisits } = buildShlinkApiClient(getState);
const visitsLoader = async (page: number, itemsPerPage: number) => getTagVisits(
const { getTagVisits: getVisits } = buildShlinkApiClient(getState);
const visitsLoader = async (page: number, itemsPerPage: number) => getVisits(
tag,
{ ...query, page, itemsPerPage },
);
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, async (params) => getTagVisits(tag, params));
const lastVisitLoader = lastVisitLoaderForLoader(doIntervalFallback, async (params) => getVisits(tag, params));
const shouldCancel = () => getState().tagVisits.cancelLoad;
const extraFinishActionData: Partial<TagVisitsAction> = { tag, query };
const actionMap = {

View File

@@ -6,11 +6,9 @@ import { buildReducer } from '../../utils/helpers/redux';
import { groupNewVisitsByType } from '../types/helpers';
import { CREATE_VISITS, CreateVisitsAction } from './visitCreation';
/* eslint-disable padding-line-between-statements */
export const GET_OVERVIEW_START = 'shlink/visitsOverview/GET_OVERVIEW_START';
export const GET_OVERVIEW_ERROR = 'shlink/visitsOverview/GET_OVERVIEW_ERROR';
export const GET_OVERVIEW = 'shlink/visitsOverview/GET_OVERVIEW';
/* eslint-enable padding-line-between-statements */
export interface VisitsOverview {
visitsCount: number;

View File

@@ -4,6 +4,7 @@ import { hasValue } from '../../utils/utils';
import { CityStats, NormalizedVisit, Stats, Visit, VisitsStats } from '../types';
import { isNormalizedOrphanVisit, isOrphanVisit } from '../types/helpers';
/* eslint-disable no-param-reassign */
const visitHasProperty = (visit: NormalizedVisit, propertyName: keyof NormalizedVisit) =>
!isNil(visit) && hasValue(visit[propertyName]);
@@ -46,10 +47,10 @@ const updateCitiesForMapForVisit = (citiesForMapStats: Record<string, CityStats>
const currentCity = citiesForMapStats[city] || {
cityName: city,
count: 0,
latLong: [ optionalNumericToNumber(latitude), optionalNumericToNumber(longitude) ],
latLong: [optionalNumericToNumber(latitude), optionalNumericToNumber(longitude)],
};
currentCity.count++;
currentCity.count += 1;
citiesForMapStats[city] = currentCity;
};

View File

@@ -19,26 +19,26 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => {
bottle.serviceFactory('ShortUrlVisits', ShortUrlVisits, 'ReportExporter');
bottle.decorator('ShortUrlVisits', connect(
[ 'shortUrlVisits', 'shortUrlDetail', 'mercureInfo', 'settings', 'selectedServer' ],
[ 'getShortUrlVisits', 'getShortUrlDetail', 'cancelGetShortUrlVisits', 'createNewVisits', 'loadMercureInfo' ],
['shortUrlVisits', 'shortUrlDetail', 'mercureInfo', 'settings', 'selectedServer'],
['getShortUrlVisits', 'getShortUrlDetail', 'cancelGetShortUrlVisits', 'createNewVisits', 'loadMercureInfo'],
));
bottle.serviceFactory('TagVisits', TagVisits, 'ColorGenerator', 'ReportExporter');
bottle.decorator('TagVisits', connect(
[ 'tagVisits', 'mercureInfo', 'settings', 'selectedServer' ],
[ 'getTagVisits', 'cancelGetTagVisits', 'createNewVisits', 'loadMercureInfo' ],
['tagVisits', 'mercureInfo', 'settings', 'selectedServer'],
['getTagVisits', 'cancelGetTagVisits', 'createNewVisits', 'loadMercureInfo'],
));
bottle.serviceFactory('OrphanVisits', OrphanVisits, 'ReportExporter');
bottle.decorator('OrphanVisits', connect(
[ 'orphanVisits', 'mercureInfo', 'settings', 'selectedServer' ],
[ 'getOrphanVisits', 'cancelGetOrphanVisits', 'createNewVisits', 'loadMercureInfo' ],
['orphanVisits', 'mercureInfo', 'settings', 'selectedServer'],
['getOrphanVisits', 'cancelGetOrphanVisits', 'createNewVisits', 'loadMercureInfo'],
));
bottle.serviceFactory('NonOrphanVisits', NonOrphanVisits, 'ReportExporter');
bottle.decorator('NonOrphanVisits', connect(
[ 'nonOrphanVisits', 'mercureInfo', 'settings', 'selectedServer' ],
[ 'getNonOrphanVisits', 'cancelGetNonOrphanVisits', 'createNewVisits', 'loadMercureInfo' ],
['nonOrphanVisits', 'mercureInfo', 'settings', 'selectedServer'],
['getNonOrphanVisits', 'cancelGetNonOrphanVisits', 'createNewVisits', 'loadMercureInfo'],
));
// Services

View File

@@ -3,10 +3,10 @@ import { formatIsoDate } from '../../utils/helpers/date';
import { ShlinkVisitsParams } from '../../api/types';
import { CreateVisit, NormalizedOrphanVisit, NormalizedVisit, OrphanVisit, Stats, Visit, VisitsParams } from './index';
export const isOrphanVisit = (visit: Visit): visit is OrphanVisit => visit.hasOwnProperty('visitedUrl');
export const isOrphanVisit = (visit: Visit): visit is OrphanVisit => !!(visit as OrphanVisit).visitedUrl;
export const isNormalizedOrphanVisit = (visit: NormalizedVisit): visit is NormalizedOrphanVisit =>
visit.hasOwnProperty('visitedUrl');
!!(visit as NormalizedOrphanVisit).visitedUrl;
export interface GroupedNewVisits {
orphanVisits: CreateVisit[];
@@ -14,7 +14,7 @@ export interface GroupedNewVisits {
}
export const groupNewVisitsByType = pipe(
groupBy((newVisit: CreateVisit) => isOrphanVisit(newVisit.visit) ? 'orphanVisits' : 'regularVisits'),
groupBy((newVisit: CreateVisit) => (isOrphanVisit(newVisit.visit) ? 'orphanVisits' : 'regularVisits')),
// @ts-expect-error Type declaration on groupBy is not correct. It can return undefined props
(result): GroupedNewVisits => ({ orphanVisits: [], regularVisits: [], ...result }),
);