1
0
mirror of https://github.com/Sonarr/Sonarr synced 2025-10-05 15:42:51 +02:00

Use react-query for History UI

This commit is contained in:
Mark McDowall
2025-09-30 21:12:38 -07:00
parent d6f309adb6
commit 16255ed53f
10 changed files with 377 additions and 471 deletions

View File

@@ -1,4 +1,4 @@
import React from 'react';
import React, { useCallback, useEffect, useRef } from 'react';
import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton';
import Modal from 'Components/Modal/Modal';
@@ -9,6 +9,7 @@ import ModalHeader from 'Components/Modal/ModalHeader';
import { kinds } from 'Helpers/Props';
import { HistoryData, HistoryEventType } from 'typings/History';
import translate from 'Utilities/String/translate';
import { useMarkAsFailed } from '../useHistory';
import HistoryDetails from './HistoryDetails';
import styles from './HistoryDetailsModal.css';
@@ -33,26 +34,32 @@ function getHeaderTitle(eventType: HistoryEventType) {
interface HistoryDetailsModalProps {
isOpen: boolean;
id: number;
eventType: HistoryEventType;
sourceTitle: string;
data: HistoryData;
downloadId?: string;
isMarkingAsFailed: boolean;
onMarkAsFailedPress: () => void;
onModalClose: () => void;
}
function HistoryDetailsModal(props: HistoryDetailsModalProps) {
const {
isOpen,
eventType,
sourceTitle,
data,
downloadId,
isMarkingAsFailed = false,
onMarkAsFailedPress,
onModalClose,
} = props;
const { isOpen, id, eventType, sourceTitle, data, downloadId, onModalClose } =
props;
const { markAsFailed, isMarkingAsFailed, markAsFailedError } =
useMarkAsFailed(id);
const wasMarkingAsFailed = useRef(isMarkingAsFailed);
const handleMarkAsFailedPress = useCallback(() => {
markAsFailed();
}, [markAsFailed]);
useEffect(() => {
if (wasMarkingAsFailed && !isMarkingAsFailed && !markAsFailedError) {
onModalClose();
}
}, [wasMarkingAsFailed, isMarkingAsFailed, markAsFailedError, onModalClose]);
return (
<Modal isOpen={isOpen} onModalClose={onModalClose}>
@@ -74,7 +81,7 @@ function HistoryDetailsModal(props: HistoryDetailsModalProps) {
className={styles.markAsFailedButton}
kind={kinds.DANGER}
isSpinning={isMarkingAsFailed}
onPress={onMarkAsFailedPress}
onPress={handleMarkAsFailedPress}
>
{translate('MarkAsFailed')}
</SpinnerButton>

View File

@@ -1,6 +1,9 @@
import React, { useCallback, useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import AppState from 'App/State/AppState';
import {
setQueueOption,
setQueueOptions,
} from 'Activity/Queue/queueOptionsStore';
import Alert from 'Components/Alert';
import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import FilterMenu from 'Components/Menu/FilterMenu';
@@ -13,20 +16,11 @@ import Table from 'Components/Table/Table';
import TableBody from 'Components/Table/TableBody';
import TableOptionsModalWrapper from 'Components/Table/TableOptions/TableOptionsModalWrapper';
import TablePager from 'Components/Table/TablePager';
import usePaging from 'Components/Table/usePaging';
import createEpisodesFetchingSelector from 'Episode/createEpisodesFetchingSelector';
import useCurrentPage from 'Helpers/Hooks/useCurrentPage';
import { align, icons, kinds } from 'Helpers/Props';
import { clearEpisodes, fetchEpisodes } from 'Store/Actions/episodeActions';
import { clearEpisodeFiles } from 'Store/Actions/episodeFileActions';
import {
clearHistory,
fetchHistory,
gotoHistoryPage,
setHistoryFilter,
setHistorySort,
setHistoryTableOption,
} from 'Store/Actions/historyActions';
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
import HistoryItem from 'typings/History';
import { TableOptionsChangePayload } from 'typings/Table';
@@ -37,100 +31,90 @@ import {
} from 'Utilities/pagePopulator';
import translate from 'Utilities/String/translate';
import HistoryFilterModal from './HistoryFilterModal';
import { useHistoryOptions } from './historyOptionsStore';
import HistoryRow from './HistoryRow';
import useHistory, { useFilters } from './useHistory';
function History() {
const requestCurrentPage = useCurrentPage();
const {
isFetching,
isPopulated,
error,
items,
columns,
selectedFilterKey,
filters,
sortKey,
sortDirection,
page,
pageSize,
records,
totalPages,
totalRecords,
} = useSelector((state: AppState) => state.history);
error,
isFetching,
isFetched,
isLoading,
page,
goToPage,
refetch,
} = useHistory();
const { columns, pageSize, sortKey, sortDirection, selectedFilterKey } =
useHistoryOptions();
const filters = useFilters();
const requestCurrentPage = useCurrentPage();
const { isEpisodesFetching, isEpisodesPopulated, episodesError } =
useSelector(createEpisodesFetchingSelector());
const customFilters = useSelector(createCustomFiltersSelector('history'));
const dispatch = useDispatch();
const isFetchingAny = isFetching || isEpisodesFetching;
const isAllPopulated = isPopulated && (isEpisodesPopulated || !items.length);
const isFetchingAny = isLoading || isEpisodesFetching;
const isAllPopulated = isFetched && (isEpisodesPopulated || !records.length);
const hasError = error || episodesError;
const {
handleFirstPagePress,
handlePreviousPagePress,
handleNextPagePress,
handleLastPagePress,
handlePageSelect,
} = usePaging({
page,
totalPages,
gotoPage: gotoHistoryPage,
});
const handleFilterSelect = useCallback(
(selectedFilterKey: string | number) => {
dispatch(setHistoryFilter({ selectedFilterKey }));
setQueueOption('selectedFilterKey', selectedFilterKey);
},
[dispatch]
[]
);
const handleSortPress = useCallback(
(sortKey: string) => {
dispatch(setHistorySort({ sortKey }));
},
[dispatch]
);
const handleSortPress = useCallback((sortKey: string) => {
setQueueOption('sortKey', sortKey);
}, []);
const handleTableOptionChange = useCallback(
(payload: TableOptionsChangePayload) => {
dispatch(setHistoryTableOption(payload));
setQueueOptions(payload);
if (payload.pageSize) {
dispatch(gotoHistoryPage({ page: 1 }));
goToPage(1);
}
},
[dispatch]
[goToPage]
);
useEffect(() => {
if (requestCurrentPage) {
dispatch(fetchHistory());
} else {
dispatch(gotoHistoryPage({ page: 1 }));
}
const handleRefreshPress = useCallback(() => {
goToPage(1);
refetch();
}, [goToPage, refetch]);
useEffect(() => {
return () => {
dispatch(clearHistory());
dispatch(clearEpisodes());
dispatch(clearEpisodeFiles());
};
}, [requestCurrentPage, dispatch]);
useEffect(() => {
const episodeIds = selectUniqueIds<HistoryItem, number>(items, 'episodeId');
const episodeIds = selectUniqueIds<HistoryItem, number>(
records,
'episodeId'
);
if (episodeIds.length) {
dispatch(fetchEpisodes({ episodeIds }));
} else {
dispatch(clearEpisodes());
}
}, [items, dispatch]);
}, [records, dispatch]);
useEffect(() => {
const repopulate = () => {
dispatch(fetchHistory());
refetch();
};
registerPagePopulator(repopulate);
@@ -138,7 +122,7 @@ function History() {
return () => {
unregisterPagePopulator(repopulate);
};
}, [dispatch]);
}, [refetch]);
return (
<PageContent title={translate('History')}>
@@ -148,7 +132,7 @@ function History() {
label={translate('Refresh')}
iconName={icons.REFRESH}
isSpinning={isFetching}
onPress={handleFirstPagePress}
onPress={handleRefreshPress}
/>
</PageToolbarSection>
@@ -186,12 +170,12 @@ function History() {
// If history isPopulated and it's empty show no history found and don't
// wait for the episodes to populate because they are never coming.
isPopulated && !hasError && !items.length ? (
isFetched && !hasError && !records.length ? (
<Alert kind={kinds.INFO}>{translate('NoHistoryFound')}</Alert>
) : null
}
{isAllPopulated && !hasError && items.length ? (
{isAllPopulated && !hasError && records.length ? (
<div>
<Table
columns={columns}
@@ -202,7 +186,7 @@ function History() {
onSortPress={handleSortPress}
>
<TableBody>
{items.map((item) => {
{records.map((item) => {
return (
<HistoryRow key={item.id} columns={columns} {...item} />
);
@@ -215,11 +199,7 @@ function History() {
totalPages={totalPages}
totalRecords={totalRecords}
isFetching={isFetching}
onFirstPagePress={handleFirstPagePress}
onPreviousPagePress={handlePreviousPagePress}
onNextPagePress={handleNextPagePress}
onLastPagePress={handleLastPagePress}
onPageSelect={handlePageSelect}
onPageSelect={goToPage}
/>
</div>
) : null}

View File

@@ -1,48 +1,25 @@
import React, { useCallback } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { createSelector } from 'reselect';
import AppState from 'App/State/AppState';
import FilterModal, { FilterModalProps } from 'Components/Filter/FilterModal';
import { setHistoryFilter } from 'Store/Actions/historyActions';
function createHistorySelector() {
return createSelector(
(state: AppState) => state.history.items,
(queueItems) => {
return queueItems;
}
);
}
function createFilterBuilderPropsSelector() {
return createSelector(
(state: AppState) => state.history.filterBuilderProps,
(filterBuilderProps) => {
return filterBuilderProps;
}
);
}
import { setHistoryOption } from './historyOptionsStore';
import useHistory, { FILTER_BUILDER } from './useHistory';
type HistoryFilterModalProps = FilterModalProps<History>;
export default function HistoryFilterModal(props: HistoryFilterModalProps) {
const sectionItems = useSelector(createHistorySelector());
const filterBuilderProps = useSelector(createFilterBuilderPropsSelector());
const dispatch = useDispatch();
const { records } = useHistory();
const dispatchSetFilter = useCallback(
(payload: { selectedFilterKey: string | number }) => {
dispatch(setHistoryFilter(payload));
({ selectedFilterKey }: { selectedFilterKey: string | number }) => {
setHistoryOption('selectedFilterKey', selectedFilterKey);
},
[dispatch]
[]
);
return (
<FilterModal
{...props}
sectionItems={sectionItems}
filterBuilderProps={filterBuilderProps}
sectionItems={records}
filterBuilderProps={FILTER_BUILDER}
customFilterType="history"
dispatchSetFilter={dispatchSetFilter}
/>

View File

@@ -1,5 +1,4 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useDispatch } from 'react-redux';
import React, { useCallback, useState } from 'react';
import IconButton from 'Components/Link/IconButton';
import RelativeDateCell from 'Components/Table/Cells/RelativeDateCell';
import TableRowCell from 'Components/Table/Cells/TableRowCell';
@@ -13,13 +12,11 @@ import EpisodeQuality from 'Episode/EpisodeQuality';
import EpisodeTitleLink from 'Episode/EpisodeTitleLink';
import SeasonEpisodeNumber from 'Episode/SeasonEpisodeNumber';
import useEpisode from 'Episode/useEpisode';
import usePrevious from 'Helpers/Hooks/usePrevious';
import { icons, tooltipPositions } from 'Helpers/Props';
import Language from 'Language/Language';
import { QualityModel } from 'Quality/Quality';
import SeriesTitleLink from 'Series/SeriesTitleLink';
import useSeries from 'Series/useSeries';
import { fetchHistory, markAsFailed } from 'Store/Actions/historyActions';
import CustomFormat from 'typings/CustomFormat';
import { HistoryData, HistoryEventType } from 'typings/History';
import formatCustomFormatScore from 'Utilities/Number/formatCustomFormatScore';
@@ -61,13 +58,9 @@ function HistoryRow(props: HistoryRowProps) {
date,
data,
downloadId,
isMarkingAsFailed = false,
markAsFailedError,
columns,
} = props;
const wasMarkingAsFailed = usePrevious(isMarkingAsFailed);
const dispatch = useDispatch();
const series = useSeries(seriesId);
const episode = useEpisode(episodeId, 'episodes');
@@ -81,23 +74,6 @@ function HistoryRow(props: HistoryRowProps) {
setIsDetailsModalOpen(false);
}, [setIsDetailsModalOpen]);
const handleMarkAsFailedPress = useCallback(() => {
dispatch(markAsFailed({ id }));
}, [id, dispatch]);
useEffect(() => {
if (wasMarkingAsFailed && !isMarkingAsFailed && !markAsFailedError) {
setIsDetailsModalOpen(false);
dispatch(fetchHistory());
}
}, [
wasMarkingAsFailed,
isMarkingAsFailed,
markAsFailedError,
setIsDetailsModalOpen,
dispatch,
]);
if (!series || !episode) {
return null;
}
@@ -254,13 +230,12 @@ function HistoryRow(props: HistoryRowProps) {
})}
<HistoryDetailsModal
id={id}
isOpen={isDetailsModalOpen}
eventType={eventType}
sourceTitle={sourceTitle}
data={data}
downloadId={downloadId}
isMarkingAsFailed={isMarkingAsFailed}
onMarkAsFailedPress={handleMarkAsFailedPress}
onModalClose={handleDetailsModalClose}
/>
</TableRow>

View File

@@ -0,0 +1,109 @@
import React from 'react';
import Icon from 'Components/Icon';
import {
createOptionsStore,
PageableOptions,
} from 'Helpers/Hooks/useOptionsStore';
import { icons } from 'Helpers/Props';
import translate from 'Utilities/String/translate';
export type HistoryOptions = PageableOptions;
const { useOptions, useOption, setOptions, setOption } =
createOptionsStore<HistoryOptions>('history_options', () => {
return {
includeUnknownSeriesItems: true,
pageSize: 20,
selectedFilterKey: 'all',
sortKey: 'time',
sortDirection: 'descending',
columns: [
{
name: 'eventType',
label: '',
columnLabel: () => translate('EventType'),
isVisible: true,
isModifiable: false,
},
{
name: 'series.sortTitle',
label: () => translate('Series'),
isSortable: true,
isVisible: true,
},
{
name: 'episode',
label: () => translate('Episode'),
isVisible: true,
},
{
name: 'episodes.title',
label: () => translate('EpisodeTitle'),
isVisible: true,
},
{
name: 'languages',
label: () => translate('Languages'),
isVisible: false,
},
{
name: 'quality',
label: () => translate('Quality'),
isVisible: true,
},
{
name: 'customFormats',
label: () => translate('Formats'),
isSortable: false,
isVisible: true,
},
{
name: 'date',
label: () => translate('Date'),
isSortable: true,
isVisible: true,
},
{
name: 'downloadClient',
label: () => translate('DownloadClient'),
isVisible: false,
},
{
name: 'indexer',
label: () => translate('Indexer'),
isVisible: false,
},
{
name: 'releaseGroup',
label: () => translate('ReleaseGroup'),
isVisible: false,
},
{
name: 'sourceTitle',
label: () => translate('SourceTitle'),
isVisible: false,
},
{
name: 'customFormatScore',
columnLabel: () => translate('CustomFormatScore'),
label: React.createElement(Icon, {
name: icons.SCORE,
title: () => translate('CustomFormatScore'),
}),
isVisible: false,
},
{
name: 'details',
label: '',
columnLabel: () => translate('Details'),
isVisible: true,
isModifiable: false,
},
],
};
});
export const useHistoryOptions = useOptions;
export const setHistoryOptions = setOptions;
export const useHistoryOption = useOption;
export const setHistoryOption = setOption;

View File

@@ -0,0 +1,186 @@
import { keepPreviousData, useQueryClient } from '@tanstack/react-query';
import { useCallback, useMemo, useState } from 'react';
import { useSelector } from 'react-redux';
import { CustomFilter, Filter, FilterBuilderProp } from 'App/State/AppState';
import useApiMutation from 'Helpers/Hooks/useApiMutation';
import usePage from 'Helpers/Hooks/usePage';
import usePagedApiQuery from 'Helpers/Hooks/usePagedApiQuery';
import { filterBuilderValueTypes } from 'Helpers/Props';
import { createCustomFiltersSelector } from 'Store/Selectors/createClientSideCollectionSelector';
import History from 'typings/History';
import findSelectedFilters from 'Utilities/Filter/findSelectedFilters';
import translate from 'Utilities/String/translate';
import { useHistoryOptions } from './historyOptionsStore';
export const FILTERS: Filter[] = [
{
key: 'all',
label: () => translate('All'),
filters: [],
},
{
key: 'grabbed',
label: () => translate('Grabbed'),
filters: [
{
key: 'eventType',
value: '1',
type: 'equal',
},
],
},
{
key: 'imported',
label: () => translate('Imported'),
filters: [
{
key: 'eventType',
value: '3',
type: 'equal',
},
],
},
{
key: 'failed',
label: () => translate('Failed'),
filters: [
{
key: 'eventType',
value: '4',
type: 'equal',
},
],
},
{
key: 'deleted',
label: () => translate('Deleted'),
filters: [
{
key: 'eventType',
value: '5',
type: 'equal',
},
],
},
{
key: 'renamed',
label: () => translate('Renamed'),
filters: [
{
key: 'eventType',
value: '6',
type: 'equal',
},
],
},
{
key: 'ignored',
label: () => translate('Ignored'),
filters: [
{
key: 'eventType',
value: '7',
type: 'equal',
},
],
},
];
export const FILTER_BUILDER: FilterBuilderProp<History>[] = [
{
name: 'eventType',
label: () => translate('EventType'),
type: 'equal',
valueType: filterBuilderValueTypes.HISTORY_EVENT_TYPE,
},
{
name: 'seriesIds',
label: () => translate('Series'),
type: 'equal',
valueType: filterBuilderValueTypes.SERIES,
},
{
name: 'quality',
label: () => translate('Quality'),
type: 'equal',
valueType: filterBuilderValueTypes.QUALITY,
},
{
name: 'languages',
label: () => translate('Languages'),
type: 'contains',
valueType: filterBuilderValueTypes.LANGUAGE,
},
];
const useHistory = () => {
const { page, goToPage } = usePage('history');
const { pageSize, selectedFilterKey, sortKey, sortDirection } =
useHistoryOptions();
const customFilters = useSelector(
createCustomFiltersSelector('history')
) as CustomFilter[];
const filters = useMemo(() => {
return findSelectedFilters(selectedFilterKey, FILTERS, customFilters);
}, [selectedFilterKey, customFilters]);
const { refetch, ...query } = usePagedApiQuery<History>({
path: '/history',
page,
pageSize,
filters,
sortKey,
sortDirection,
queryOptions: {
placeholderData: keepPreviousData,
},
});
const handleGoToPage = useCallback(
(page: number) => {
goToPage(page);
},
[goToPage]
);
return {
...query,
goToPage: handleGoToPage,
page,
refetch,
};
};
export default useHistory;
export const useFilters = () => {
return FILTERS;
};
export const useMarkAsFailed = (id: number) => {
const queryClient = useQueryClient();
const [error, setError] = useState<string | null>(null);
const { mutate, isPending } = useApiMutation<unknown, void>({
path: `/history/failed/${id}`,
method: 'POST',
mutationOptions: {
onMutate: () => {
setError(null);
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['/history'] });
},
onError: () => {
setError('Error marking history item as failed');
},
},
});
return {
markAsFailed: mutate,
isMarkingAsFailed: isPending,
markAsFailedError: error,
};
};

View File

@@ -90,7 +90,6 @@ interface AppState {
episodeHistory: HistoryAppState;
episodes: EpisodesAppState;
episodesSelection: EpisodesAppState;
history: HistoryAppState;
importSeries: ImportSeriesAppState;
interactiveImport: InteractiveImportAppState;
oAuth: OAuthAppState;

View File

@@ -5,12 +5,14 @@ import { create } from 'zustand';
interface PageStore {
blocklist: number;
events: number;
history: number;
queue: number;
}
const pageStore = create<PageStore>(() => ({
blocklist: 1,
events: 1,
history: 1,
queue: 1,
}));

View File

@@ -1,327 +0,0 @@
import React from 'react';
import { createAction } from 'redux-actions';
import Icon from 'Components/Icon';
import { filterBuilderTypes, filterBuilderValueTypes, filterTypes, icons, sortDirections } from 'Helpers/Props';
import { createThunk, handleThunks } from 'Store/thunks';
import createAjaxRequest from 'Utilities/createAjaxRequest';
import serverSideCollectionHandlers from 'Utilities/State/serverSideCollectionHandlers';
import translate from 'Utilities/String/translate';
import { updateItem } from './baseActions';
import createHandleActions from './Creators/createHandleActions';
import createServerSideCollectionHandlers from './Creators/createServerSideCollectionHandlers';
import createClearReducer from './Creators/Reducers/createClearReducer';
import createSetTableOptionReducer from './Creators/Reducers/createSetTableOptionReducer';
//
// Variables
export const section = 'history';
//
// State
export const defaultState = {
isFetching: false,
isPopulated: false,
error: null,
pageSize: 20,
sortKey: 'date',
sortDirection: sortDirections.DESCENDING,
items: [],
columns: [
{
name: 'eventType',
columnLabel: () => translate('EventType'),
isVisible: true,
isModifiable: false
},
{
name: 'series.sortTitle',
label: () => translate('Series'),
isSortable: true,
isVisible: true
},
{
name: 'episode',
label: () => translate('Episode'),
isVisible: true
},
{
name: 'episodes.title',
label: () => translate('EpisodeTitle'),
isVisible: true
},
{
name: 'languages',
label: () => translate('Languages'),
isVisible: false
},
{
name: 'quality',
label: () => translate('Quality'),
isVisible: true
},
{
name: 'customFormats',
label: () => translate('Formats'),
isSortable: false,
isVisible: true
},
{
name: 'date',
label: () => translate('Date'),
isSortable: true,
isVisible: true
},
{
name: 'downloadClient',
label: () => translate('DownloadClient'),
isVisible: false
},
{
name: 'indexer',
label: () => translate('Indexer'),
isVisible: false
},
{
name: 'releaseGroup',
label: () => translate('ReleaseGroup'),
isVisible: false
},
{
name: 'sourceTitle',
label: () => translate('SourceTitle'),
isVisible: false
},
{
name: 'customFormatScore',
columnLabel: () => translate('CustomFormatScore'),
label: React.createElement(Icon, {
name: icons.SCORE,
title: () => translate('CustomFormatScore')
}),
isVisible: false
},
{
name: 'details',
columnLabel: () => translate('Details'),
isVisible: true,
isModifiable: false
}
],
selectedFilterKey: 'all',
filters: [
{
key: 'all',
label: () => translate('All'),
filters: []
},
{
key: 'grabbed',
label: () => translate('Grabbed'),
filters: [
{
key: 'eventType',
value: '1',
type: filterTypes.EQUAL
}
]
},
{
key: 'imported',
label: () => translate('Imported'),
filters: [
{
key: 'eventType',
value: '3',
type: filterTypes.EQUAL
}
]
},
{
key: 'failed',
label: () => translate('Failed'),
filters: [
{
key: 'eventType',
value: '4',
type: filterTypes.EQUAL
}
]
},
{
key: 'deleted',
label: () => translate('Deleted'),
filters: [
{
key: 'eventType',
value: '5',
type: filterTypes.EQUAL
}
]
},
{
key: 'renamed',
label: () => translate('Renamed'),
filters: [
{
key: 'eventType',
value: '6',
type: filterTypes.EQUAL
}
]
},
{
key: 'ignored',
label: () => translate('Ignored'),
filters: [
{
key: 'eventType',
value: '7',
type: filterTypes.EQUAL
}
]
}
],
filterBuilderProps: [
{
name: 'eventType',
label: () => translate('EventType'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.HISTORY_EVENT_TYPE
},
{
name: 'seriesIds',
label: () => translate('Series'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.SERIES
},
{
name: 'quality',
label: () => translate('Quality'),
type: filterBuilderTypes.EQUAL,
valueType: filterBuilderValueTypes.QUALITY
},
{
name: 'languages',
label: () => translate('Languages'),
type: filterBuilderTypes.CONTAINS,
valueType: filterBuilderValueTypes.LANGUAGE
}
]
};
export const persistState = [
'history.pageSize',
'history.sortKey',
'history.sortDirection',
'history.selectedFilterKey',
'history.columns'
];
//
// Actions Types
export const FETCH_HISTORY = 'history/fetchHistory';
export const GOTO_FIRST_HISTORY_PAGE = 'history/gotoHistoryFirstPage';
export const GOTO_PREVIOUS_HISTORY_PAGE = 'history/gotoHistoryPreviousPage';
export const GOTO_NEXT_HISTORY_PAGE = 'history/gotoHistoryNextPage';
export const GOTO_LAST_HISTORY_PAGE = 'history/gotoHistoryLastPage';
export const GOTO_HISTORY_PAGE = 'history/gotoHistoryPage';
export const SET_HISTORY_SORT = 'history/setHistorySort';
export const SET_HISTORY_FILTER = 'history/setHistoryFilter';
export const SET_HISTORY_TABLE_OPTION = 'history/setHistoryTableOption';
export const CLEAR_HISTORY = 'history/clearHistory';
export const MARK_AS_FAILED = 'history/markAsFailed';
//
// Action Creators
export const fetchHistory = createThunk(FETCH_HISTORY);
export const gotoHistoryFirstPage = createThunk(GOTO_FIRST_HISTORY_PAGE);
export const gotoHistoryPreviousPage = createThunk(GOTO_PREVIOUS_HISTORY_PAGE);
export const gotoHistoryNextPage = createThunk(GOTO_NEXT_HISTORY_PAGE);
export const gotoHistoryLastPage = createThunk(GOTO_LAST_HISTORY_PAGE);
export const gotoHistoryPage = createThunk(GOTO_HISTORY_PAGE);
export const setHistorySort = createThunk(SET_HISTORY_SORT);
export const setHistoryFilter = createThunk(SET_HISTORY_FILTER);
export const setHistoryTableOption = createAction(SET_HISTORY_TABLE_OPTION);
export const clearHistory = createAction(CLEAR_HISTORY);
export const markAsFailed = createThunk(MARK_AS_FAILED);
//
// Action Handlers
export const actionHandlers = handleThunks({
...createServerSideCollectionHandlers(
section,
'/history',
fetchHistory,
{
[serverSideCollectionHandlers.FETCH]: FETCH_HISTORY,
[serverSideCollectionHandlers.FIRST_PAGE]: GOTO_FIRST_HISTORY_PAGE,
[serverSideCollectionHandlers.PREVIOUS_PAGE]: GOTO_PREVIOUS_HISTORY_PAGE,
[serverSideCollectionHandlers.NEXT_PAGE]: GOTO_NEXT_HISTORY_PAGE,
[serverSideCollectionHandlers.LAST_PAGE]: GOTO_LAST_HISTORY_PAGE,
[serverSideCollectionHandlers.EXACT_PAGE]: GOTO_HISTORY_PAGE,
[serverSideCollectionHandlers.SORT]: SET_HISTORY_SORT,
[serverSideCollectionHandlers.FILTER]: SET_HISTORY_FILTER
}),
[MARK_AS_FAILED]: function(getState, payload, dispatch) {
const id = payload.id;
dispatch(updateItem({
section,
id,
isMarkingAsFailed: true
}));
const promise = createAjaxRequest({
url: `/history/failed/${id}`,
method: 'POST',
dataType: 'json'
}).request;
promise.done(() => {
dispatch(updateItem({
section,
id,
isMarkingAsFailed: false,
markAsFailedError: null
}));
});
promise.fail((xhr) => {
dispatch(updateItem({
section,
id,
isMarkingAsFailed: false,
markAsFailedError: xhr
}));
});
}
});
//
// Reducers
export const reducers = createHandleActions({
[SET_HISTORY_TABLE_OPTION]: createSetTableOptionReducer(section),
[CLEAR_HISTORY]: createClearReducer(section, {
isFetching: false,
isPopulated: false,
error: null,
items: [],
totalPages: 0,
totalRecords: 0
})
}, defaultState, section);

View File

@@ -7,7 +7,6 @@ import * as episodes from './episodeActions';
import * as episodeFiles from './episodeFileActions';
import * as episodeHistory from './episodeHistoryActions';
import * as episodeSelection from './episodeSelectionActions';
import * as history from './historyActions';
import * as importSeries from './importSeriesActions';
import * as interactiveImportActions from './interactiveImportActions';
import * as oAuth from './oAuthActions';
@@ -35,7 +34,6 @@ export default [
episodeFiles,
episodeHistory,
episodeSelection,
history,
importSeries,
interactiveImportActions,
oAuth,