Add pagination support to history page

pull/1515/head
LASER-Yi 3 years ago
parent 156cf1882c
commit 8348118ff2

@ -1419,7 +1419,8 @@ class EpisodesHistory(Resource):
if episodeid: if episodeid:
query_conditions.append((TableEpisodes.sonarrEpisodeId == episodeid)) query_conditions.append((TableEpisodes.sonarrEpisodeId == episodeid))
query_condition = reduce(operator.and_, query_conditions) query_condition = reduce(operator.and_, query_conditions)
episode_history = TableHistory.select(TableShows.title.alias('seriesTitle'), episode_history = TableHistory.select(TableHistory.id,
TableShows.title.alias('seriesTitle'),
TableEpisodes.monitored, TableEpisodes.monitored,
TableEpisodes.season.concat('x').concat(TableEpisodes.episode).alias('episode_number'), TableEpisodes.season.concat('x').concat(TableEpisodes.episode).alias('episode_number'),
TableEpisodes.title.alias('episodeTitle'), TableEpisodes.title.alias('episodeTitle'),
@ -1535,7 +1536,8 @@ class MoviesHistory(Resource):
query_conditions.append((TableMovies.radarrId == radarrid)) query_conditions.append((TableMovies.radarrId == radarrid))
query_condition = reduce(operator.and_, query_conditions) query_condition = reduce(operator.and_, query_conditions)
movie_history = TableHistoryMovie.select(TableHistoryMovie.action, movie_history = TableHistoryMovie.select(TableHistoryMovie.id,
TableHistoryMovie.action,
TableMovies.title, TableMovies.title,
TableHistoryMovie.timestamp, TableHistoryMovie.timestamp,
TableHistoryMovie.description, TableHistoryMovie.description,

@ -55,15 +55,17 @@ export const movieUpdateWantedByRange = createAsyncThunk(
} }
); );
export const movieUpdateHistory = createAsyncThunk( export const movieUpdateHistoryByRange = createAsyncThunk(
"movies/history/update", "movies/history/update/range",
async () => { async (params: Parameter.Range) => {
const response = await MoviesApi.history(); const response = await MoviesApi.history(params);
return response; return response;
} }
); );
export const movieMarkHistoryDirty = createAction("movies/history/mark_dirty"); export const movieMarkHistoryDirty = createAction<number[]>(
"movies/history/mark_dirty"
);
export const movieUpdateBlacklist = createAsyncThunk( export const movieUpdateBlacklist = createAsyncThunk(
"movies/blacklist/update", "movies/blacklist/update",

@ -77,15 +77,17 @@ export const episodeUpdateById = createAsyncThunk(
} }
); );
export const episodesUpdateHistory = createAsyncThunk( export const episodesUpdateHistoryByRange = createAsyncThunk(
"episodes/history/update", "episodes/history/update/range",
async () => { async (param: Parameter.Range) => {
const response = await EpisodesApi.history(); const response = await EpisodesApi.history(param);
return response; return response;
} }
); );
export const episodesMarkHistoryDirty = createAction("episodes/history/update"); export const episodesMarkHistoryDirty = createAction<number[]>(
"episodes/history/update"
);
export const episodesUpdateBlacklist = createAsyncThunk( export const episodesUpdateBlacklist = createAsyncThunk(
"episodes/blacklist/update", "episodes/blacklist/update",

@ -3,7 +3,6 @@ import { useEntityItemById, useEntityToList } from "../../utilites";
import { import {
movieUpdateBlacklist, movieUpdateBlacklist,
movieUpdateById, movieUpdateById,
movieUpdateHistory,
movieUpdateWantedById, movieUpdateWantedById,
} from "../actions"; } from "../actions";
import { useAutoDirtyUpdate, useAutoUpdate } from "./async"; import { useAutoDirtyUpdate, useAutoUpdate } from "./async";
@ -62,9 +61,7 @@ export function useBlacklistMovies() {
} }
export function useMoviesHistory() { export function useMoviesHistory() {
const update = useReduxAction(movieUpdateHistory);
const items = useReduxStore((s) => s.movies.historyList); const items = useReduxStore((s) => s.movies.historyList);
useAutoUpdate(items, update);
return items; return items;
} }

@ -2,7 +2,6 @@ import { useCallback, useEffect, useMemo } from "react";
import { useEntityItemById, useEntityToList } from "../../utilites"; import { useEntityItemById, useEntityToList } from "../../utilites";
import { import {
episodesUpdateBlacklist, episodesUpdateBlacklist,
episodesUpdateHistory,
episodeUpdateById, episodeUpdateById,
episodeUpdateBySeriesId, episodeUpdateBySeriesId,
seriesUpdateById, seriesUpdateById,
@ -94,9 +93,7 @@ export function useBlacklistSeries() {
} }
export function useSeriesHistory() { export function useSeriesHistory() {
const update = useReduxAction(episodesUpdateHistory);
const items = useReduxStore((s) => s.series.historyList); const items = useReduxStore((s) => s.series.historyList);
useAutoUpdate(items, update);
return items; return items;
} }

@ -10,7 +10,7 @@ import {
movieUpdateBlacklist, movieUpdateBlacklist,
movieUpdateById, movieUpdateById,
movieUpdateByRange, movieUpdateByRange,
movieUpdateHistory, movieUpdateHistoryByRange,
movieUpdateWantedById, movieUpdateWantedById,
movieUpdateWantedByRange, movieUpdateWantedByRange,
} from "../actions"; } from "../actions";
@ -23,14 +23,14 @@ import {
interface Movie { interface Movie {
movieList: Async.Entity<Item.Movie>; movieList: Async.Entity<Item.Movie>;
wantedMovieList: Async.Entity<Wanted.Movie>; wantedMovieList: Async.Entity<Wanted.Movie>;
historyList: Async.Item<History.Movie[]>; historyList: Async.Entity<History.Movie>;
blacklist: Async.Item<Blacklist.Movie[]>; blacklist: Async.Item<Blacklist.Movie[]>;
} }
const defaultMovie: Movie = { const defaultMovie: Movie = {
movieList: AsyncUtility.getDefaultEntity("radarrId"), movieList: AsyncUtility.getDefaultEntity("radarrId"),
wantedMovieList: AsyncUtility.getDefaultEntity("radarrId"), wantedMovieList: AsyncUtility.getDefaultEntity("radarrId"),
historyList: AsyncUtility.getDefaultItem(), historyList: AsyncUtility.getDefaultEntity("id"),
blacklist: AsyncUtility.getDefaultItem(), blacklist: AsyncUtility.getDefaultItem(),
}; };
@ -50,8 +50,8 @@ const reducer = createReducer(defaultMovie, (builder) => {
dirty: movieMarkWantedDirtyById, dirty: movieMarkWantedDirtyById,
}); });
createAsyncItemReducer(builder, (s) => s.historyList, { createAsyncEntityReducer(builder, (s) => s.historyList, {
all: movieUpdateHistory, range: movieUpdateHistoryByRange,
dirty: movieMarkHistoryDirty, dirty: movieMarkHistoryDirty,
}); });

@ -5,7 +5,7 @@ import {
episodesMarkHistoryDirty, episodesMarkHistoryDirty,
episodesRemoveById, episodesRemoveById,
episodesUpdateBlacklist, episodesUpdateBlacklist,
episodesUpdateHistory, episodesUpdateHistoryByRange,
episodeUpdateById, episodeUpdateById,
episodeUpdateBySeriesId, episodeUpdateBySeriesId,
seriesMarkDirtyById, seriesMarkDirtyById,
@ -29,7 +29,7 @@ interface Series {
seriesList: Async.Entity<Item.Series>; seriesList: Async.Entity<Item.Series>;
wantedEpisodesList: Async.Entity<Wanted.Episode>; wantedEpisodesList: Async.Entity<Wanted.Episode>;
episodeList: Async.List<Item.Episode>; episodeList: Async.List<Item.Episode>;
historyList: Async.Item<History.Episode[]>; historyList: Async.Entity<History.Episode>;
blacklist: Async.Item<Blacklist.Episode[]>; blacklist: Async.Item<Blacklist.Episode[]>;
} }
@ -37,7 +37,7 @@ const defaultSeries: Series = {
seriesList: AsyncUtility.getDefaultEntity("sonarrSeriesId"), seriesList: AsyncUtility.getDefaultEntity("sonarrSeriesId"),
wantedEpisodesList: AsyncUtility.getDefaultEntity("sonarrEpisodeId"), wantedEpisodesList: AsyncUtility.getDefaultEntity("sonarrEpisodeId"),
episodeList: AsyncUtility.getDefaultList("sonarrEpisodeId"), episodeList: AsyncUtility.getDefaultList("sonarrEpisodeId"),
historyList: AsyncUtility.getDefaultItem(), historyList: AsyncUtility.getDefaultEntity("id"),
blacklist: AsyncUtility.getDefaultItem(), blacklist: AsyncUtility.getDefaultItem(),
}; };
@ -72,8 +72,8 @@ const reducer = createReducer(defaultSeries, (builder) => {
dirty: seriesMarkWantedDirtyById, dirty: seriesMarkWantedDirtyById,
}); });
createAsyncItemReducer(builder, (s) => s.historyList, { createAsyncEntityReducer(builder, (s) => s.historyList, {
all: episodesUpdateHistory, range: episodesUpdateHistoryByRange,
dirty: episodesMarkHistoryDirty, dirty: episodesMarkHistoryDirty,
}); });

@ -2,11 +2,9 @@ import { ActionCreator } from "@reduxjs/toolkit";
import { import {
episodesMarkBlacklistDirty, episodesMarkBlacklistDirty,
episodesMarkDirtyById, episodesMarkDirtyById,
episodesMarkHistoryDirty,
episodesRemoveById, episodesRemoveById,
movieMarkBlacklistDirty, movieMarkBlacklistDirty,
movieMarkDirtyById, movieMarkDirtyById,
movieMarkHistoryDirty,
movieMarkWantedDirtyById, movieMarkWantedDirtyById,
movieRemoveById, movieRemoveById,
movieRemoveWantedById, movieRemoveWantedById,
@ -130,7 +128,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
}, },
{ {
key: "movie-history", key: "movie-history",
any: bindReduxAction(movieMarkHistoryDirty), // any: bindReduxAction(movieMarkHistoryDirty),
}, },
{ {
key: "movie-blacklist", key: "movie-blacklist",
@ -138,7 +136,7 @@ export function createDefaultReducer(): SocketIO.Reducer[] {
}, },
{ {
key: "episode-history", key: "episode-history",
any: bindReduxAction(episodesMarkHistoryDirty), // any: bindReduxAction(episodesMarkHistoryDirty),
}, },
{ {
key: "episode-blacklist", key: "episode-blacklist",

@ -195,6 +195,7 @@ declare namespace History {
TagType & TagType &
MonitoredType & MonitoredType &
Partial<ItemHistoryType> & { Partial<ItemHistoryType> & {
id: number;
action: number; action: number;
blacklisted: boolean; blacklisted: boolean;
score?: string; score?: string;

@ -4,7 +4,9 @@ import React, { FunctionComponent, useMemo } from "react";
import { Badge, OverlayTrigger, Popover } from "react-bootstrap"; import { Badge, OverlayTrigger, Popover } from "react-bootstrap";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Column } from "react-table"; import { Column } from "react-table";
import { movieUpdateHistoryByRange } from "../../@redux/actions";
import { useMoviesHistory } from "../../@redux/hooks"; import { useMoviesHistory } from "../../@redux/hooks";
import { useReduxAction } from "../../@redux/hooks/base";
import { MoviesApi } from "../../apis"; import { MoviesApi } from "../../apis";
import { HistoryIcon, LanguageText, TextPopover } from "../../components"; import { HistoryIcon, LanguageText, TextPopover } from "../../components";
import { BlacklistButton } from "../../generic/blacklist"; import { BlacklistButton } from "../../generic/blacklist";
@ -14,6 +16,7 @@ interface Props {}
const MoviesHistoryView: FunctionComponent<Props> = () => { const MoviesHistoryView: FunctionComponent<Props> = () => {
const movies = useMoviesHistory(); const movies = useMoviesHistory();
const loader = useReduxAction(movieUpdateHistoryByRange);
const columns: Column<History.Movie>[] = useMemo<Column<History.Movie>[]>( const columns: Column<History.Movie>[] = useMemo<Column<History.Movie>[]>(
() => [ () => [
@ -128,7 +131,8 @@ const MoviesHistoryView: FunctionComponent<Props> = () => {
<HistoryGenericView <HistoryGenericView
type="movies" type="movies"
state={movies} state={movies}
columns={columns as Column<History.Base>[]} loader={loader}
columns={columns}
></HistoryGenericView> ></HistoryGenericView>
); );
}; };

@ -4,7 +4,9 @@ import React, { FunctionComponent, useMemo } from "react";
import { Badge, OverlayTrigger, Popover } from "react-bootstrap"; import { Badge, OverlayTrigger, Popover } from "react-bootstrap";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { Column } from "react-table"; import { Column } from "react-table";
import { episodesUpdateHistoryByRange } from "../../@redux/actions";
import { useSeriesHistory } from "../../@redux/hooks"; import { useSeriesHistory } from "../../@redux/hooks";
import { useReduxAction } from "../../@redux/hooks/base";
import { EpisodesApi } from "../../apis"; import { EpisodesApi } from "../../apis";
import { HistoryIcon, LanguageText, TextPopover } from "../../components"; import { HistoryIcon, LanguageText, TextPopover } from "../../components";
import { BlacklistButton } from "../../generic/blacklist"; import { BlacklistButton } from "../../generic/blacklist";
@ -14,6 +16,7 @@ interface Props {}
const SeriesHistoryView: FunctionComponent<Props> = () => { const SeriesHistoryView: FunctionComponent<Props> = () => {
const series = useSeriesHistory(); const series = useSeriesHistory();
const loader = useReduxAction(episodesUpdateHistoryByRange);
const columns: Column<History.Episode>[] = useMemo<Column<History.Episode>[]>( const columns: Column<History.Episode>[] = useMemo<Column<History.Episode>[]>(
() => [ () => [
@ -137,7 +140,8 @@ const SeriesHistoryView: FunctionComponent<Props> = () => {
<HistoryGenericView <HistoryGenericView
type="series" type="series"
state={series} state={series}
columns={columns as Column<History.Base>[]} loader={loader}
columns={columns}
></HistoryGenericView> ></HistoryGenericView>
); );
}; };

@ -1,21 +1,23 @@
import { capitalize } from "lodash"; import { capitalize } from "lodash";
import React, { FunctionComponent } from "react"; import React from "react";
import { Container, Row } from "react-bootstrap"; import { Container, Row } from "react-bootstrap";
import { Helmet } from "react-helmet"; import { Helmet } from "react-helmet";
import { Column } from "react-table"; import { Column } from "react-table";
import { AsyncOverlay, PageTable } from "../../components"; import { AsyncPageTable } from "../../components";
interface Props { interface Props<T extends History.Base> {
type: "movies" | "series"; type: "movies" | "series";
state: Readonly<Async.Item<History.Base[]>>; state: Readonly<Async.Entity<T>>;
columns: Column<History.Base>[]; loader: (param: Parameter.Range) => void;
columns: Column<T>[];
} }
const HistoryGenericView: FunctionComponent<Props> = ({ function HistoryGenericView<T extends History.Base = History.Base>({
state, state,
loader,
columns, columns,
type, type,
}) => { }: Props<T>) {
const typeName = capitalize(type); const typeName = capitalize(type);
return ( return (
<Container fluid> <Container fluid>
@ -23,18 +25,16 @@ const HistoryGenericView: FunctionComponent<Props> = ({
<title>{typeName} History - Bazarr</title> <title>{typeName} History - Bazarr</title>
</Helmet> </Helmet>
<Row> <Row>
<AsyncOverlay ctx={state}> <AsyncPageTable
{({ content }) => (
<PageTable
emptyText={`Nothing Found in ${typeName} History`} emptyText={`Nothing Found in ${typeName} History`}
entity={state}
loader={loader}
columns={columns} columns={columns}
data={content ?? []} data={[]}
></PageTable> ></AsyncPageTable>
)}
</AsyncOverlay>
</Row> </Row>
</Container> </Container>
); );
}; }
export default HistoryGenericView; export default HistoryGenericView;

@ -53,16 +53,20 @@ class EpisodeApi extends BaseApi {
}); });
} }
async history(episodeid?: number): Promise<History.Episode[]> { async history(params: Parameter.Range) {
return new Promise<History.Episode[]>((resolve, reject) => { const response = await this.get<AsyncDataWrapper<History.Episode>>(
this.get<DataWrapper<History.Episode[]>>("/history", { episodeid }) "/history",
.then((result) => { params
resolve(result.data.data); );
}) return response.data;
.catch((reason) => { }
reject(reason);
}); async historyBy(episodeid: number) {
}); const response = await this.get<AsyncDataWrapper<History.Episode>>(
"/history",
{ episodeid }
);
return response.data;
} }
async downloadSubtitles( async downloadSubtitles(

@ -93,18 +93,20 @@ class MovieApi extends BaseApi {
}); });
} }
async history(id?: number): Promise<History.Movie[]> { async history(params: Parameter.Range) {
return new Promise<History.Movie[]>((resolve, reject) => { const response = await this.get<AsyncDataWrapper<History.Movie>>(
this.get<DataWrapper<History.Movie[]>>("/history", { "/history",
radarrid: id, params
}) );
.then((result) => { return response.data;
resolve(result.data.data); }
})
.catch((reason) => { async historyBy(radarrid: number) {
reject(reason); const response = await this.get<AsyncDataWrapper<History.Movie>>(
}); "/history",
}); { radarrid }
);
return response.data;
} }
async action(action: FormType.MoviesAction) { async action(action: FormType.MoviesAction) {

@ -14,8 +14,8 @@ export const MovieHistoryModal: FunctionComponent<BaseModalProps> = (props) => {
const movie = useModalPayload<Item.Movie>(modal.modalKey); const movie = useModalPayload<Item.Movie>(modal.modalKey);
const [history, updateHistory] = useAsyncRequest( const [history, updateHistory] = useAsyncRequest(
MoviesApi.history.bind(MoviesApi), MoviesApi.historyBy.bind(MoviesApi),
[] { data: [], total: 0 }
); );
const update = useCallback(() => { const update = useCallback(() => {
@ -98,7 +98,7 @@ export const MovieHistoryModal: FunctionComponent<BaseModalProps> = (props) => {
<PageTable <PageTable
emptyText="No History Found" emptyText="No History Found"
columns={columns} columns={columns}
data={content} data={content.data}
></PageTable> ></PageTable>
)} )}
</AsyncOverlay> </AsyncOverlay>
@ -114,8 +114,8 @@ export const EpisodeHistoryModal: FunctionComponent<
const episode = useModalPayload<Item.Episode>(props.modalKey); const episode = useModalPayload<Item.Episode>(props.modalKey);
const [history, updateHistory] = useAsyncRequest( const [history, updateHistory] = useAsyncRequest(
EpisodesApi.history.bind(EpisodesApi), EpisodesApi.historyBy.bind(EpisodesApi),
[] { data: [], total: 0 }
); );
const update = useCallback(() => { const update = useCallback(() => {
@ -199,7 +199,7 @@ export const EpisodeHistoryModal: FunctionComponent<
<PageTable <PageTable
emptyText="No History Found" emptyText="No History Found"
columns={columns} columns={columns}
data={content} data={content.data}
></PageTable> ></PageTable>
)} )}
</AsyncOverlay> </AsyncOverlay>

Loading…
Cancel
Save