1
0
mirror of https://github.com/Radarr/Radarr.git synced 2024-09-17 15:02:34 +02:00

Fixed: Import movie spinning forever when error is returned

Fixes #4993

Co-Authored-By: Mark McDowall <markus101@users.noreply.github.com>
This commit is contained in:
Qstick 2020-09-11 22:44:45 -04:00
parent 4be83a3367
commit 69071768de
4 changed files with 104 additions and 17 deletions

View File

@ -31,3 +31,7 @@
margin: 0 10px 0 12px; margin: 0 10px 0 12px;
text-align: left; text-align: left;
} }
.importError {
margin-left: 10px;
}

View File

@ -3,11 +3,13 @@ import PropTypes from 'prop-types';
import React, { Component } from 'react'; import React, { Component } from 'react';
// import CheckInput from 'Components/Form/CheckInput'; // import CheckInput from 'Components/Form/CheckInput';
import FormInputGroup from 'Components/Form/FormInputGroup'; import FormInputGroup from 'Components/Form/FormInputGroup';
import Icon from 'Components/Icon';
import Button from 'Components/Link/Button'; import Button from 'Components/Link/Button';
import SpinnerButton from 'Components/Link/SpinnerButton'; import SpinnerButton from 'Components/Link/SpinnerButton';
import LoadingIndicator from 'Components/Loading/LoadingIndicator'; import LoadingIndicator from 'Components/Loading/LoadingIndicator';
import PageContentFooter from 'Components/Page/PageContentFooter'; import PageContentFooter from 'Components/Page/PageContentFooter';
import { inputTypes, kinds } from 'Helpers/Props'; import Popover from 'Components/Tooltip/Popover';
import { icons, inputTypes, kinds, tooltipPositions } from 'Helpers/Props';
import styles from './ImportMovieFooter.css'; import styles from './ImportMovieFooter.css';
const MIXED = 'mixed'; const MIXED = 'mixed';
@ -93,7 +95,10 @@ class ImportMovieFooter extends Component {
isMonitorMixed, isMonitorMixed,
isQualityProfileIdMixed, isQualityProfileIdMixed,
isMinimumAvailabilityMixed, isMinimumAvailabilityMixed,
hasUnsearchedItems,
importError,
onImportPress, onImportPress,
onLookupPress,
onCancelLookupPress onCancelLookupPress
} = this.props; } = this.props;
@ -167,27 +172,71 @@ class ImportMovieFooter extends Component {
</SpinnerButton> </SpinnerButton>
{ {
isLookingUpMovie && isLookingUpMovie ?
<Button <Button
className={styles.loadingButton} className={styles.loadingButton}
kind={kinds.WARNING} kind={kinds.WARNING}
onPress={onCancelLookupPress} onPress={onCancelLookupPress}
> >
Cancel Processing Cancel Processing
</Button> </Button> :
null
} }
{ {
isLookingUpMovie && hasUnsearchedItems ?
<Button
className={styles.loadingButton}
kind={kinds.SUCCESS}
onPress={onLookupPress}
>
Start Processing
</Button> :
null
}
{
isLookingUpMovie ?
<LoadingIndicator <LoadingIndicator
className={styles.loading} className={styles.loading}
size={24} size={24}
/> /> :
null
} }
{ {
isLookingUpMovie && isLookingUpMovie ?
'Processing Folders' 'Processing Folders' :
null
}
{
importError ?
<Popover
anchor={
<Icon
className={styles.importError}
name={icons.WARNING}
kind={kinds.WARNING}
/>
}
title="Import Errors"
body={
<ul>
{
importError.responseJSON.map((error, index) => {
return (
<li key={index}>
{error.errorMessage}
</li>
);
})
}
</ul>
}
position={tooltipPositions.RIGHT}
/> :
null
} }
</div> </div>
</div> </div>
@ -206,8 +255,11 @@ ImportMovieFooter.propTypes = {
isMonitorMixed: PropTypes.bool.isRequired, isMonitorMixed: PropTypes.bool.isRequired,
isQualityProfileIdMixed: PropTypes.bool.isRequired, isQualityProfileIdMixed: PropTypes.bool.isRequired,
isMinimumAvailabilityMixed: PropTypes.bool.isRequired, isMinimumAvailabilityMixed: PropTypes.bool.isRequired,
hasUnsearchedItems: PropTypes.bool.isRequired,
importError: PropTypes.object,
onInputChange: PropTypes.func.isRequired, onInputChange: PropTypes.func.isRequired,
onImportPress: PropTypes.func.isRequired, onImportPress: PropTypes.func.isRequired,
onLookupPress: PropTypes.func.isRequired,
onCancelLookupPress: PropTypes.func.isRequired onCancelLookupPress: PropTypes.func.isRequired
}; };

View File

@ -1,7 +1,7 @@
import _ from 'lodash'; import _ from 'lodash';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import { cancelLookupMovie } from 'Store/Actions/importMovieActions'; import { cancelLookupMovie, lookupUnsearchedMovies } from 'Store/Actions/importMovieActions';
import ImportMovieFooter from './ImportMovieFooter'; import ImportMovieFooter from './ImportMovieFooter';
function isMixed(items, selectedIds, defaultValue, key) { function isMixed(items, selectedIds, defaultValue, key) {
@ -25,12 +25,14 @@ function createMapStateToProps() {
const { const {
isLookingUpMovie, isLookingUpMovie,
isImporting, isImporting,
items items,
importError
} = importMovie; } = importMovie;
const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor'); const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor');
const isQualityProfileIdMixed = isMixed(items, selectedIds, defaultQualityProfileId, 'qualityProfileId'); const isQualityProfileIdMixed = isMixed(items, selectedIds, defaultQualityProfileId, 'qualityProfileId');
const isMinimumAvailabilityMixed = isMixed(items, selectedIds, defaultMinimumAvailability, 'minimumAvailability'); const isMinimumAvailabilityMixed = isMixed(items, selectedIds, defaultMinimumAvailability, 'minimumAvailability');
const hasUnsearchedItems = !isLookingUpMovie && items.some((item) => !item.isPopulated);
return { return {
selectedCount: selectedIds.length, selectedCount: selectedIds.length,
@ -41,13 +43,16 @@ function createMapStateToProps() {
defaultMinimumAvailability, defaultMinimumAvailability,
isMonitorMixed, isMonitorMixed,
isQualityProfileIdMixed, isQualityProfileIdMixed,
isMinimumAvailabilityMixed isMinimumAvailabilityMixed,
importError,
hasUnsearchedItems
}; };
} }
); );
} }
const mapDispatchToProps = { const mapDispatchToProps = {
onLookupPress: lookupUnsearchedMovies,
onCancelLookupPress: cancelLookupMovie onCancelLookupPress: cancelLookupMovie
}; };

View File

@ -35,6 +35,7 @@ export const defaultState = {
export const QUEUE_LOOKUP_MOVIE = 'importMovie/queueLookupMovie'; export const QUEUE_LOOKUP_MOVIE = 'importMovie/queueLookupMovie';
export const START_LOOKUP_MOVIE = 'importMovie/startLookupMovie'; export const START_LOOKUP_MOVIE = 'importMovie/startLookupMovie';
export const CANCEL_LOOKUP_MOVIE = 'importMovie/cancelLookupMovie'; export const CANCEL_LOOKUP_MOVIE = 'importMovie/cancelLookupMovie';
export const LOOKUP_UNSEARCHED_MOVIES = 'importMovie/lookupUnsearchedMovies';
export const CLEAR_IMPORT_MOVIE = 'importMovie/clearImportMovie'; export const CLEAR_IMPORT_MOVIE = 'importMovie/clearImportMovie';
export const SET_IMPORT_MOVIE_VALUE = 'importMovie/setImportMovieValue'; export const SET_IMPORT_MOVIE_VALUE = 'importMovie/setImportMovieValue';
export const IMPORT_MOVIE = 'importMovie/importMovie'; export const IMPORT_MOVIE = 'importMovie/importMovie';
@ -45,6 +46,7 @@ export const IMPORT_MOVIE = 'importMovie/importMovie';
export const queueLookupMovie = createThunk(QUEUE_LOOKUP_MOVIE); export const queueLookupMovie = createThunk(QUEUE_LOOKUP_MOVIE);
export const startLookupMovie = createThunk(START_LOOKUP_MOVIE); export const startLookupMovie = createThunk(START_LOOKUP_MOVIE);
export const importMovie = createThunk(IMPORT_MOVIE); export const importMovie = createThunk(IMPORT_MOVIE);
export const lookupUnsearchedMovies = createThunk(LOOKUP_UNSEARCHED_MOVIES);
export const clearImportMovie = createAction(CLEAR_IMPORT_MOVIE); export const clearImportMovie = createAction(CLEAR_IMPORT_MOVIE);
export const cancelLookupMovie = createAction(CANCEL_LOOKUP_MOVIE); export const cancelLookupMovie = createAction(CANCEL_LOOKUP_MOVIE);
@ -179,6 +181,29 @@ export const actionHandlers = handleThunks({
}); });
}, },
[LOOKUP_UNSEARCHED_MOVIES]: function(getState, payload, dispatch) {
const state = getState().importMovie;
if (state.isLookingUpMovie) {
return;
}
state.items.forEach((item) => {
const id = item.id;
if (
!item.isPopulated &&
!queue.includes(id)
) {
queue.push(item.id);
}
});
if (queue.length) {
dispatch(startLookupMovie({ start: true }));
}
},
[IMPORT_MOVIE]: function(getState, payload, dispatch) { [IMPORT_MOVIE]: function(getState, payload, dispatch) {
dispatch(set({ section, isImporting: true })); dispatch(set({ section, isImporting: true }));
@ -215,7 +240,8 @@ export const actionHandlers = handleThunks({
set({ set({
section, section,
isImporting: false, isImporting: false,
isImported: true isImported: true,
importError: null
}), }),
...data.map((movie) => updateItem({ section: 'movies', ...movie })), ...data.map((movie) => updateItem({ section: 'movies', ...movie })),
@ -227,19 +253,19 @@ export const actionHandlers = handleThunks({
}); });
promise.fail((xhr) => { promise.fail((xhr) => {
dispatch(batchActions( dispatch(batchActions([
set({ set({
section, section,
isImporting: false, isImporting: false,
isImported: true isImported: true,
importError: xhr
}), }),
addedIds.map((id) => updateItem({ ...addedIds.map((id) => updateItem({
section, section,
id, id
importError: xhr
})) }))
)); ]));
}); });
} }
}); });