From 69071768de5d2ed96f3fc66f9c26c374e0be58d6 Mon Sep 17 00:00:00 2001 From: Qstick Date: Fri, 11 Sep 2020 22:44:45 -0400 Subject: [PATCH] Fixed: Import movie spinning forever when error is returned Fixes #4993 Co-Authored-By: Mark McDowall --- .../ImportMovie/Import/ImportMovieFooter.css | 4 ++ .../ImportMovie/Import/ImportMovieFooter.js | 66 +++++++++++++++++-- .../Import/ImportMovieFooterConnector.js | 11 +++- .../src/Store/Actions/importMovieActions.js | 40 +++++++++-- 4 files changed, 104 insertions(+), 17 deletions(-) diff --git a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooter.css b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooter.css index 616aeaf3c..9bfb5a493 100644 --- a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooter.css +++ b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooter.css @@ -31,3 +31,7 @@ margin: 0 10px 0 12px; text-align: left; } + +.importError { + margin-left: 10px; +} diff --git a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooter.js b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooter.js index f3c211a9a..d3b1eb1e8 100644 --- a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooter.js +++ b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooter.js @@ -3,11 +3,13 @@ import PropTypes from 'prop-types'; import React, { Component } from 'react'; // import CheckInput from 'Components/Form/CheckInput'; import FormInputGroup from 'Components/Form/FormInputGroup'; +import Icon from 'Components/Icon'; import Button from 'Components/Link/Button'; import SpinnerButton from 'Components/Link/SpinnerButton'; import LoadingIndicator from 'Components/Loading/LoadingIndicator'; 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'; const MIXED = 'mixed'; @@ -93,7 +95,10 @@ class ImportMovieFooter extends Component { isMonitorMixed, isQualityProfileIdMixed, isMinimumAvailabilityMixed, + hasUnsearchedItems, + importError, onImportPress, + onLookupPress, onCancelLookupPress } = this.props; @@ -167,27 +172,71 @@ class ImportMovieFooter extends Component { { - isLookingUpMovie && + isLookingUpMovie ? + : + null } { - isLookingUpMovie && + hasUnsearchedItems ? + : + null + } + + { + isLookingUpMovie ? + /> : + null } { - isLookingUpMovie && - 'Processing Folders' + isLookingUpMovie ? + 'Processing Folders' : + null + } + + { + importError ? + + } + title="Import Errors" + body={ +
    + { + importError.responseJSON.map((error, index) => { + return ( +
  • + {error.errorMessage} +
  • + ); + }) + } +
+ } + position={tooltipPositions.RIGHT} + /> : + null } @@ -206,8 +255,11 @@ ImportMovieFooter.propTypes = { isMonitorMixed: PropTypes.bool.isRequired, isQualityProfileIdMixed: PropTypes.bool.isRequired, isMinimumAvailabilityMixed: PropTypes.bool.isRequired, + hasUnsearchedItems: PropTypes.bool.isRequired, + importError: PropTypes.object, onInputChange: PropTypes.func.isRequired, onImportPress: PropTypes.func.isRequired, + onLookupPress: PropTypes.func.isRequired, onCancelLookupPress: PropTypes.func.isRequired }; diff --git a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooterConnector.js b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooterConnector.js index 3c0c58325..f43cfa8e4 100644 --- a/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooterConnector.js +++ b/frontend/src/AddMovie/ImportMovie/Import/ImportMovieFooterConnector.js @@ -1,7 +1,7 @@ import _ from 'lodash'; import { connect } from 'react-redux'; import { createSelector } from 'reselect'; -import { cancelLookupMovie } from 'Store/Actions/importMovieActions'; +import { cancelLookupMovie, lookupUnsearchedMovies } from 'Store/Actions/importMovieActions'; import ImportMovieFooter from './ImportMovieFooter'; function isMixed(items, selectedIds, defaultValue, key) { @@ -25,12 +25,14 @@ function createMapStateToProps() { const { isLookingUpMovie, isImporting, - items + items, + importError } = importMovie; const isMonitorMixed = isMixed(items, selectedIds, defaultMonitor, 'monitor'); const isQualityProfileIdMixed = isMixed(items, selectedIds, defaultQualityProfileId, 'qualityProfileId'); const isMinimumAvailabilityMixed = isMixed(items, selectedIds, defaultMinimumAvailability, 'minimumAvailability'); + const hasUnsearchedItems = !isLookingUpMovie && items.some((item) => !item.isPopulated); return { selectedCount: selectedIds.length, @@ -41,13 +43,16 @@ function createMapStateToProps() { defaultMinimumAvailability, isMonitorMixed, isQualityProfileIdMixed, - isMinimumAvailabilityMixed + isMinimumAvailabilityMixed, + importError, + hasUnsearchedItems }; } ); } const mapDispatchToProps = { + onLookupPress: lookupUnsearchedMovies, onCancelLookupPress: cancelLookupMovie }; diff --git a/frontend/src/Store/Actions/importMovieActions.js b/frontend/src/Store/Actions/importMovieActions.js index 20d172f6d..a3aa59a86 100644 --- a/frontend/src/Store/Actions/importMovieActions.js +++ b/frontend/src/Store/Actions/importMovieActions.js @@ -35,6 +35,7 @@ export const defaultState = { export const QUEUE_LOOKUP_MOVIE = 'importMovie/queueLookupMovie'; export const START_LOOKUP_MOVIE = 'importMovie/startLookupMovie'; export const CANCEL_LOOKUP_MOVIE = 'importMovie/cancelLookupMovie'; +export const LOOKUP_UNSEARCHED_MOVIES = 'importMovie/lookupUnsearchedMovies'; export const CLEAR_IMPORT_MOVIE = 'importMovie/clearImportMovie'; export const SET_IMPORT_MOVIE_VALUE = 'importMovie/setImportMovieValue'; 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 startLookupMovie = createThunk(START_LOOKUP_MOVIE); export const importMovie = createThunk(IMPORT_MOVIE); +export const lookupUnsearchedMovies = createThunk(LOOKUP_UNSEARCHED_MOVIES); export const clearImportMovie = createAction(CLEAR_IMPORT_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) { dispatch(set({ section, isImporting: true })); @@ -215,7 +240,8 @@ export const actionHandlers = handleThunks({ set({ section, isImporting: false, - isImported: true + isImported: true, + importError: null }), ...data.map((movie) => updateItem({ section: 'movies', ...movie })), @@ -227,19 +253,19 @@ export const actionHandlers = handleThunks({ }); promise.fail((xhr) => { - dispatch(batchActions( + dispatch(batchActions([ set({ section, isImporting: false, - isImported: true + isImported: true, + importError: xhr }), - addedIds.map((id) => updateItem({ + ...addedIds.map((id) => updateItem({ section, - id, - importError: xhr + id })) - )); + ])); }); } });