feat: Created a History tab in the Radarr UI and created a list history command and mark-history-item-as-failed command for Radarr

This commit is contained in:
2026-01-13 12:35:54 -07:00
parent 0172253d20
commit ad9e2b3671
52 changed files with 2265 additions and 84 deletions
+5
View File
@@ -62,6 +62,11 @@ impl App<'_> {
.dispatch_network_event(RadarrEvent::GetDownloads(500).into())
.await;
}
ActiveRadarrBlock::History => {
self
.dispatch_network_event(RadarrEvent::GetHistory(500).into())
.await;
}
ActiveRadarrBlock::Indexers => {
self
.dispatch_network_event(RadarrEvent::GetTags.into())
+6 -5
View File
@@ -3,8 +3,8 @@ mod tests {
use crate::app::App;
use crate::app::context_clues::{
BARE_POPUP_CONTEXT_CLUES, BLOCKLIST_CONTEXT_CLUES, CONFIRMATION_PROMPT_CONTEXT_CLUES,
ContextClue, ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
ContextClue, ContextClueProvider, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
};
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::app::radarr::radarr_context_clues::{
@@ -459,9 +459,10 @@ mod tests {
#[case(1, ActiveRadarrBlock::Collections, &COLLECTIONS_CONTEXT_CLUES)]
#[case(2, ActiveRadarrBlock::Downloads, &DOWNLOADS_CONTEXT_CLUES)]
#[case(3, ActiveRadarrBlock::Blocklist, &BLOCKLIST_CONTEXT_CLUES)]
#[case(4, ActiveRadarrBlock::RootFolders, &ROOT_FOLDERS_CONTEXT_CLUES)]
#[case(5, ActiveRadarrBlock::Indexers, &INDEXERS_CONTEXT_CLUES)]
#[case(6, ActiveRadarrBlock::System, &SYSTEM_CONTEXT_CLUES)]
#[case(4, ActiveRadarrBlock::History, &HISTORY_CONTEXT_CLUES)]
#[case(5, ActiveRadarrBlock::RootFolders, &ROOT_FOLDERS_CONTEXT_CLUES)]
#[case(6, ActiveRadarrBlock::Indexers, &INDEXERS_CONTEXT_CLUES)]
#[case(7, ActiveRadarrBlock::System, &SYSTEM_CONTEXT_CLUES)]
fn test_radarr_context_clue_provider_radarr_blocks_context_clues(
#[case] index: usize,
#[case] active_radarr_block: ActiveRadarrBlock,
+17
View File
@@ -147,6 +147,23 @@ mod tests {
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_history_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
app
.dispatch_by_radarr_block(&ActiveRadarrBlock::History)
.await;
assert!(app.is_loading);
assert_eq!(
sync_network_rx.recv().await.unwrap(),
RadarrEvent::GetHistory(500).into()
);
assert!(!app.data.radarr_data.prompt_confirm);
assert_eq!(app.tick_count, 0);
}
#[tokio::test]
async fn test_dispatch_by_root_folders_block() {
let (mut app, mut sync_network_rx) = construct_app_unit();
+12
View File
@@ -29,6 +29,11 @@ pub enum RadarrListCommand {
},
#[command(about = "List disk space details for all provisioned root folders in Radarr")]
DiskSpace,
#[command(about = "Fetch all Radarr history events")]
History {
#[arg(long, help = "How many history events to fetch", default_value_t = 500)]
events: u64,
},
#[command(about = "List all Radarr indexers")]
Indexers,
#[command(about = "Fetch Radarr logs")]
@@ -121,6 +126,13 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrListCommand> for RadarrListCommandH
.await?;
serde_json::to_string_pretty(&resp)?
}
RadarrListCommand::History { events: items } => {
let resp = self
.network
.handle_network_event(RadarrEvent::GetHistory(items).into())
.await?;
serde_json::to_string_pretty(&resp)?
}
RadarrListCommand::Indexers => {
let resp = self
.network
@@ -111,6 +111,29 @@ mod tests {
assert_eq!(refresh_command, expected_args);
}
#[test]
fn test_list_history_events_flag_requires_arguments() {
let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "list", "history", "--events"]);
assert_err!(&result);
assert_eq!(result.unwrap_err().kind(), ErrorKind::InvalidValue);
}
#[test]
fn test_list_history_default_values() {
let expected_args = RadarrListCommand::History { events: 500 };
let result = Cli::try_parse_from(["managarr", "radarr", "list", "history"]);
assert_ok!(&result);
let Some(Command::Radarr(RadarrCommand::List(history_command))) = result.unwrap().command
else {
panic!("Unexpected command type");
};
assert_eq!(history_command, expected_args);
}
#[test]
fn test_list_logs_default_values() {
let expected_args = RadarrListCommand::Logs {
@@ -233,6 +256,32 @@ mod tests {
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_list_history_command() {
let expected_events = 1000;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
RadarrEvent::GetHistory(expected_events).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Radarr(RadarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let list_history_command = RadarrListCommand::History { events: 1000 };
let result =
RadarrListCommandHandler::with(&app_arc, list_history_command, &mut mock_network)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_handle_list_logs_command() {
let expected_events = 1000;
+18
View File
@@ -64,6 +64,15 @@ pub enum RadarrCommand {
Refresh(RadarrRefreshCommand),
#[command(about = "Clear the blocklist")]
ClearBlocklist,
#[command(about = "Mark the Radarr history item with the given ID as 'failed'")]
MarkHistoryItemAsFailed {
#[arg(
long,
help = "The Radarr ID of the history item you wish to mark as 'failed'",
required = true
)]
history_item_id: i64,
},
#[command(about = "Manually download the given release for the specified movie ID")]
DownloadRelease {
#[arg(long, help = "The GUID of the release to download", required = true)]
@@ -208,6 +217,15 @@ impl<'a, 'b> CliCommandHandler<'a, 'b, RadarrCommand> for RadarrCliHandler<'a, '
.await?;
serde_json::to_string_pretty(&resp)?
}
RadarrCommand::MarkHistoryItemAsFailed { history_item_id } => {
let _ = self
.network
.handle_network_event(RadarrEvent::MarkHistoryItemAsFailed(history_item_id).into())
.await?;
serde_json::to_string_pretty(
&serde_json::json!({"message": "Radarr history item marked as 'failed'"}),
)?
}
RadarrCommand::DownloadRelease {
guid,
indexer_id,
+55
View File
@@ -31,6 +31,31 @@ mod tests {
assert_ok!(&result);
}
#[test]
fn test_mark_history_item_as_failed_requires_history_item_id() {
let result =
Cli::command().try_get_matches_from(["managarr", "radarr", "mark-history-item-as-failed"]);
assert_err!(&result);
assert_eq!(
result.unwrap_err().kind(),
ErrorKind::MissingRequiredArgument
);
}
#[test]
fn test_mark_history_item_as_failed_requirements_satisfied() {
let result = Cli::command().try_get_matches_from([
"managarr",
"radarr",
"mark-history-item-as-failed",
"--history-item-id",
"1",
]);
assert_ok!(&result);
}
#[test]
fn test_download_release_requires_movie_id() {
let result = Cli::command().try_get_matches_from([
@@ -327,6 +352,36 @@ mod tests {
assert_ok!(&result);
}
#[tokio::test]
async fn test_mark_history_item_as_failed_command() {
let expected_history_item_id = 1;
let mut mock_network = MockNetworkTrait::new();
mock_network
.expect_handle_network_event()
.with(eq::<NetworkEvent>(
RadarrEvent::MarkHistoryItemAsFailed(expected_history_item_id).into(),
))
.times(1)
.returning(|_| {
Ok(Serdeable::Radarr(RadarrSerdeable::Value(
json!({"testResponse": "response"}),
)))
});
let app_arc = Arc::new(Mutex::new(App::test_default()));
let mark_history_item_as_failed_command =
RadarrCommand::MarkHistoryItemAsFailed { history_item_id: 1 };
let result = RadarrCliHandler::with(
&app_arc,
mark_history_item_as_failed_command,
&mut mock_network,
)
.handle()
.await;
assert_ok!(&result);
}
#[tokio::test]
async fn test_download_release_command() {
let expected_release_download_body = RadarrReleaseDownloadBody {
+3
View File
@@ -356,6 +356,9 @@ mod test_utils {
.radarr_data
.movies
.set_items(vec![$crate::models::radarr_models::Movie::default()]);
app.data.radarr_data.history.set_items(vec![
$crate::models::radarr_models::RadarrHistoryItem::default(),
]);
app
.data
.radarr_data
@@ -91,9 +91,9 @@ mod tests {
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
ActiveRadarrBlock::RootFolders.into()
ActiveRadarrBlock::History.into()
);
assert_navigation_pushed!(app, ActiveRadarrBlock::RootFolders.into());
assert_navigation_pushed!(app, ActiveRadarrBlock::History.into());
}
#[rstest]
@@ -0,0 +1,438 @@
#[cfg(test)]
mod tests {
use std::cmp::Ordering;
use chrono::DateTime;
use pretty_assertions::{assert_eq, assert_str_eq};
use rstest::rstest;
use strum::IntoEnumIterator;
use crate::app::App;
use crate::app::key_binding::DEFAULT_KEYBINDINGS;
use crate::assert_navigation_pushed;
use crate::event::Key;
use crate::handlers::KeyEventHandler;
use crate::handlers::radarr_handlers::history::{HistoryHandler, history_sorting_options};
use crate::models::radarr_models::{RadarrHistoryEventType, RadarrHistoryItem};
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, HISTORY_BLOCKS};
use crate::models::servarr_models::{Language, Quality, QualityWrapper};
mod test_handle_left_right_action {
use pretty_assertions::assert_eq;
use rstest::rstest;
use super::*;
use crate::assert_navigation_pushed;
#[rstest]
fn test_history_tab_left(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::History.into());
app.is_loading = is_ready;
app.data.radarr_data.main_tabs.set_index(4);
HistoryHandler::new(
DEFAULT_KEYBINDINGS.left.key,
&mut app,
ActiveRadarrBlock::History,
None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
ActiveRadarrBlock::Blocklist.into()
);
assert_navigation_pushed!(app, ActiveRadarrBlock::Blocklist.into());
}
#[rstest]
fn test_history_tab_right(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::History.into());
app.is_loading = is_ready;
app.data.radarr_data.main_tabs.set_index(4);
HistoryHandler::new(
DEFAULT_KEYBINDINGS.right.key,
&mut app,
ActiveRadarrBlock::History,
None,
)
.handle();
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
ActiveRadarrBlock::RootFolders.into()
);
assert_eq!(
app.get_current_route(),
ActiveRadarrBlock::RootFolders.into()
);
}
}
mod test_handle_submit {
use pretty_assertions::assert_eq;
use super::*;
const SUBMIT_KEY: Key = DEFAULT_KEYBINDINGS.submit.key;
#[test]
fn test_history_submit() {
let mut app = App::test_default();
app.data.radarr_data.history.set_items(history_vec());
app.push_navigation_stack(ActiveRadarrBlock::History.into());
HistoryHandler::new(SUBMIT_KEY, &mut app, ActiveRadarrBlock::History, None).handle();
assert_navigation_pushed!(app, ActiveRadarrBlock::HistoryItemDetails.into());
}
#[test]
fn test_history_submit_no_op_when_not_ready() {
let mut app = App::test_default();
app.is_loading = true;
app.data.radarr_data.history.set_items(history_vec());
app.push_navigation_stack(ActiveRadarrBlock::History.into());
HistoryHandler::new(SUBMIT_KEY, &mut app, ActiveRadarrBlock::History, None).handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::History.into());
}
}
mod test_handle_esc {
use pretty_assertions::assert_eq;
use rstest::rstest;
use crate::models::servarr_data::radarr::radarr_data::radarr_test_utils::utils::create_test_radarr_data;
use super::*;
use crate::assert_navigation_popped;
const ESC_KEY: Key = DEFAULT_KEYBINDINGS.esc.key;
#[test]
fn test_esc_history_item_details() {
let mut app = App::test_default();
app
.data
.radarr_data
.history
.set_items(vec![RadarrHistoryItem::default()]);
app.push_navigation_stack(ActiveRadarrBlock::History.into());
app.push_navigation_stack(ActiveRadarrBlock::HistoryItemDetails.into());
HistoryHandler::new(
ESC_KEY,
&mut app,
ActiveRadarrBlock::HistoryItemDetails,
None,
)
.handle();
assert_navigation_popped!(app, ActiveRadarrBlock::History.into());
}
#[rstest]
fn test_default_esc(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.error = "test error".to_owned().into();
app.push_navigation_stack(ActiveRadarrBlock::History.into());
app.push_navigation_stack(ActiveRadarrBlock::History.into());
app.data.radarr_data = create_test_radarr_data();
app
.data
.radarr_data
.history
.set_items(vec![RadarrHistoryItem::default()]);
HistoryHandler::new(ESC_KEY, &mut app, ActiveRadarrBlock::History, None).handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::History.into());
assert_is_empty!(app.error.text);
}
}
mod test_handle_key_char {
use pretty_assertions::assert_eq;
use super::*;
use crate::assert_navigation_pushed;
#[test]
fn test_refresh_history_key() {
let mut app = App::test_default();
app.data.radarr_data.history.set_items(history_vec());
app.push_navigation_stack(ActiveRadarrBlock::History.into());
HistoryHandler::new(
DEFAULT_KEYBINDINGS.refresh.key,
&mut app,
ActiveRadarrBlock::History,
None,
)
.handle();
assert_navigation_pushed!(app, ActiveRadarrBlock::History.into());
assert!(app.should_refresh);
}
#[test]
fn test_refresh_history_key_no_op_when_not_ready() {
let mut app = App::test_default();
app.is_loading = true;
app.data.radarr_data.history.set_items(history_vec());
app.push_navigation_stack(ActiveRadarrBlock::History.into());
HistoryHandler::new(
DEFAULT_KEYBINDINGS.refresh.key,
&mut app,
ActiveRadarrBlock::History,
None,
)
.handle();
assert_eq!(app.get_current_route(), ActiveRadarrBlock::History.into());
assert!(!app.should_refresh);
}
}
#[test]
fn test_history_sorting_options_source_title() {
let expected_cmp_fn: fn(&RadarrHistoryItem, &RadarrHistoryItem) -> Ordering = |a, b| {
a.source_title
.text
.to_lowercase()
.cmp(&b.source_title.text.to_lowercase())
};
let mut expected_history_vec = history_vec();
expected_history_vec.sort_by(expected_cmp_fn);
let sort_option = history_sorting_options()[0].clone();
let mut sorted_history_vec = history_vec();
sorted_history_vec.sort_by(sort_option.cmp_fn.unwrap());
assert_eq!(sorted_history_vec, expected_history_vec);
assert_str_eq!(sort_option.name, "Source Title");
}
#[test]
fn test_history_sorting_options_event_type() {
let expected_cmp_fn: fn(&RadarrHistoryItem, &RadarrHistoryItem) -> Ordering = |a, b| {
a.event_type
.to_string()
.to_lowercase()
.cmp(&b.event_type.to_string().to_lowercase())
};
let mut expected_history_vec = history_vec();
expected_history_vec.sort_by(expected_cmp_fn);
let sort_option = history_sorting_options()[1].clone();
let mut sorted_history_vec = history_vec();
sorted_history_vec.sort_by(sort_option.cmp_fn.unwrap());
assert_eq!(sorted_history_vec, expected_history_vec);
assert_str_eq!(sort_option.name, "Event Type");
}
#[test]
fn test_history_sorting_options_language() {
let expected_cmp_fn: fn(&RadarrHistoryItem, &RadarrHistoryItem) -> Ordering = |a, b| {
let default_language = Language {
id: 1,
name: "_".to_owned(),
};
let language_a = a.languages.first().unwrap_or(&default_language);
let language_b = b.languages.first().unwrap_or(&default_language);
language_a.cmp(language_b)
};
let mut expected_history_vec = history_vec();
expected_history_vec.sort_by(expected_cmp_fn);
let sort_option = history_sorting_options()[2].clone();
let mut sorted_history_vec = history_vec();
sorted_history_vec.sort_by(sort_option.cmp_fn.unwrap());
assert_eq!(sorted_history_vec, expected_history_vec);
assert_str_eq!(sort_option.name, "Language");
}
#[test]
fn test_history_sorting_options_quality() {
let expected_cmp_fn: fn(&RadarrHistoryItem, &RadarrHistoryItem) -> Ordering = |a, b| {
a.quality
.quality
.name
.to_lowercase()
.cmp(&b.quality.quality.name.to_lowercase())
};
let mut expected_history_vec = history_vec();
expected_history_vec.sort_by(expected_cmp_fn);
let sort_option = history_sorting_options()[3].clone();
let mut sorted_history_vec = history_vec();
sorted_history_vec.sort_by(sort_option.cmp_fn.unwrap());
assert_eq!(sorted_history_vec, expected_history_vec);
assert_str_eq!(sort_option.name, "Quality");
}
#[test]
fn test_history_sorting_options_date() {
let expected_cmp_fn: fn(&RadarrHistoryItem, &RadarrHistoryItem) -> Ordering =
|a, b| a.date.cmp(&b.date);
let mut expected_history_vec = history_vec();
expected_history_vec.sort_by(expected_cmp_fn);
let sort_option = history_sorting_options()[4].clone();
let mut sorted_history_vec = history_vec();
sorted_history_vec.sort_by(sort_option.cmp_fn.unwrap());
assert_eq!(sorted_history_vec, expected_history_vec);
assert_str_eq!(sort_option.name, "Date");
}
#[test]
fn test_history_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if HISTORY_BLOCKS.contains(&active_radarr_block) {
assert!(HistoryHandler::accepts(active_radarr_block));
} else {
assert!(!HistoryHandler::accepts(active_radarr_block));
}
})
}
#[rstest]
fn test_history_handler_ignore_special_keys(
#[values(true, false)] ignore_special_keys_for_textbox_input: bool,
) {
let mut app = App::test_default();
app.ignore_special_keys_for_textbox_input = ignore_special_keys_for_textbox_input;
let handler = HistoryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::default(),
None,
);
assert_eq!(
handler.ignore_special_keys(),
ignore_special_keys_for_textbox_input
);
}
#[test]
fn test_history_handler_not_ready_when_loading() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::History.into());
app.is_loading = true;
let handler = HistoryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::History,
None,
);
assert!(!handler.is_ready());
}
#[test]
fn test_history_handler_not_ready_when_history_is_empty() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::History.into());
app.is_loading = false;
let handler = HistoryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::History,
None,
);
assert!(!handler.is_ready());
}
#[test]
fn test_history_handler_ready_when_not_loading_and_history_is_not_empty() {
let mut app = App::test_default();
app.push_navigation_stack(ActiveRadarrBlock::History.into());
app.is_loading = false;
app
.data
.radarr_data
.history
.set_items(vec![RadarrHistoryItem::default()]);
let handler = HistoryHandler::new(
DEFAULT_KEYBINDINGS.esc.key,
&mut app,
ActiveRadarrBlock::History,
None,
);
assert!(handler.is_ready());
}
fn history_vec() -> Vec<RadarrHistoryItem> {
vec![
RadarrHistoryItem {
id: 3,
source_title: "test 1".into(),
movie_id: 1,
event_type: RadarrHistoryEventType::Grabbed,
languages: vec![Language {
id: 1,
name: "telgu".to_owned(),
}],
quality: QualityWrapper {
quality: Quality {
name: "HD - 1080p".to_owned(),
},
},
date: DateTime::from(DateTime::parse_from_rfc3339("2024-01-10T07:28:45Z").unwrap()),
..RadarrHistoryItem::default()
},
RadarrHistoryItem {
id: 2,
source_title: "test 2".into(),
movie_id: 2,
event_type: RadarrHistoryEventType::DownloadFolderImported,
languages: vec![Language {
id: 3,
name: "chinese".to_owned(),
}],
quality: QualityWrapper {
quality: Quality {
name: "SD - 720p".to_owned(),
},
},
date: DateTime::from(DateTime::parse_from_rfc3339("2024-02-10T07:28:45Z").unwrap()),
..RadarrHistoryItem::default()
},
RadarrHistoryItem {
id: 1,
source_title: "test 3".into(),
movie_id: 3,
event_type: RadarrHistoryEventType::MovieFileDeleted,
languages: vec![Language {
id: 1,
name: "english".to_owned(),
}],
quality: QualityWrapper {
quality: Quality {
name: "HD - 1080p".to_owned(),
},
},
date: DateTime::from(DateTime::parse_from_rfc3339("2024-03-10T07:28:45Z").unwrap()),
..RadarrHistoryItem::default()
},
]
}
}
+179
View File
@@ -0,0 +1,179 @@
use crate::app::App;
use crate::event::Key;
use crate::handlers::radarr_handlers::handle_change_tab_left_right_keys;
use crate::handlers::table_handler::{TableHandlingConfig, handle_table};
use crate::handlers::{KeyEventHandler, handle_clear_errors};
use crate::matches_key;
use crate::models::Route;
use crate::models::radarr_models::RadarrHistoryItem;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, HISTORY_BLOCKS};
use crate::models::servarr_models::Language;
use crate::models::stateful_table::SortOption;
#[cfg(test)]
#[path = "history_handler_tests.rs"]
mod history_handler_tests;
pub(super) struct HistoryHandler<'a, 'b> {
key: Key,
app: &'a mut App<'b>,
active_radarr_block: ActiveRadarrBlock,
_context: Option<ActiveRadarrBlock>,
}
impl HistoryHandler<'_, '_> {}
impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for HistoryHandler<'a, 'b> {
fn handle(&mut self) {
let history_table_handling_config = TableHandlingConfig::new(ActiveRadarrBlock::History.into())
.sorting_block(ActiveRadarrBlock::HistorySortPrompt.into())
.sort_options(history_sorting_options())
.searching_block(ActiveRadarrBlock::SearchHistory.into())
.search_error_block(ActiveRadarrBlock::SearchHistoryError.into())
.search_field_fn(|history| &history.source_title.text)
.filtering_block(ActiveRadarrBlock::FilterHistory.into())
.filter_error_block(ActiveRadarrBlock::FilterHistoryError.into())
.filter_field_fn(|history| &history.source_title.text);
if !handle_table(
self,
|app| &mut app.data.radarr_data.history,
history_table_handling_config,
) {
self.handle_key_event();
}
}
fn accepts(active_block: ActiveRadarrBlock) -> bool {
HISTORY_BLOCKS.contains(&active_block)
}
fn new(
key: Key,
app: &'a mut App<'b>,
active_block: ActiveRadarrBlock,
context: Option<ActiveRadarrBlock>,
) -> Self {
HistoryHandler {
key,
app,
active_radarr_block: active_block,
_context: context,
}
}
fn get_key(&self) -> Key {
self.key
}
fn ignore_special_keys(&self) -> bool {
self.app.ignore_special_keys_for_textbox_input
}
fn is_ready(&self) -> bool {
!self.app.is_loading && !self.app.data.radarr_data.history.is_empty()
}
fn handle_scroll_up(&mut self) {}
fn handle_scroll_down(&mut self) {}
fn handle_home(&mut self) {}
fn handle_end(&mut self) {}
fn handle_delete(&mut self) {}
fn handle_left_right_action(&mut self) {
if self.active_radarr_block == ActiveRadarrBlock::History {
handle_change_tab_left_right_keys(self.app, self.key)
}
}
fn handle_submit(&mut self) {
if self.active_radarr_block == ActiveRadarrBlock::History {
self
.app
.push_navigation_stack(ActiveRadarrBlock::HistoryItemDetails.into());
}
}
fn handle_esc(&mut self) {
if self.active_radarr_block == ActiveRadarrBlock::HistoryItemDetails {
self.app.pop_navigation_stack();
} else {
handle_clear_errors(self.app);
}
}
fn handle_char_key_event(&mut self) {
let key = self.key;
if self.active_radarr_block == ActiveRadarrBlock::History {
match self.key {
_ if matches_key!(refresh, key) => {
self.app.should_refresh = true;
}
_ => (),
}
}
}
fn app_mut(&mut self) -> &mut App<'b> {
self.app
}
fn current_route(&self) -> Route {
self.app.get_current_route()
}
}
pub(in crate::handlers::radarr_handlers) fn history_sorting_options()
-> Vec<SortOption<RadarrHistoryItem>> {
vec![
SortOption {
name: "Source Title",
cmp_fn: Some(|a, b| {
a.source_title
.text
.to_lowercase()
.cmp(&b.source_title.text.to_lowercase())
}),
},
SortOption {
name: "Event Type",
cmp_fn: Some(|a, b| {
a.event_type
.to_string()
.to_lowercase()
.cmp(&b.event_type.to_string().to_lowercase())
}),
},
SortOption {
name: "Language",
cmp_fn: Some(|a, b| {
let default_language = Language {
id: 1,
name: "_".to_owned(),
};
let language_a = a.languages.first().unwrap_or(&default_language);
let language_b = b.languages.first().unwrap_or(&default_language);
language_a.cmp(language_b)
}),
},
SortOption {
name: "Quality",
cmp_fn: Some(|a, b| {
a.quality
.quality
.name
.to_lowercase()
.cmp(&b.quality.quality.name.to_lowercase())
}),
},
SortOption {
name: "Date",
cmp_fn: Some(|a, b| a.date.cmp(&b.date)),
},
]
}
@@ -65,7 +65,7 @@ mod tests {
fn test_indexers_tab_left(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.data.radarr_data.main_tabs.set_index(5);
app.data.radarr_data.main_tabs.set_index(6);
IndexersHandler::new(
DEFAULT_KEYBINDINGS.left.key,
@@ -86,7 +86,7 @@ mod tests {
fn test_indexers_tab_right(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.data.radarr_data.main_tabs.set_index(5);
app.data.radarr_data.main_tabs.set_index(6);
IndexersHandler::new(
DEFAULT_KEYBINDINGS.right.key,
+5
View File
@@ -2,6 +2,7 @@ use crate::handlers::KeyEventHandler;
use crate::handlers::radarr_handlers::blocklist::BlocklistHandler;
use crate::handlers::radarr_handlers::collections::CollectionsHandler;
use crate::handlers::radarr_handlers::downloads::DownloadsHandler;
use crate::handlers::radarr_handlers::history::HistoryHandler;
use crate::handlers::radarr_handlers::indexers::IndexersHandler;
use crate::handlers::radarr_handlers::library::LibraryHandler;
use crate::handlers::radarr_handlers::root_folders::RootFoldersHandler;
@@ -13,6 +14,7 @@ use crate::{App, Key, matches_key};
mod blocklist;
mod collections;
mod downloads;
mod history;
mod indexers;
mod library;
mod root_folders;
@@ -51,6 +53,9 @@ impl<'a, 'b> KeyEventHandler<'a, 'b, ActiveRadarrBlock> for RadarrHandler<'a, 'b
_ if DownloadsHandler::accepts(self.active_radarr_block) => {
DownloadsHandler::new(self.key, self.app, self.active_radarr_block, self.context).handle()
}
_ if HistoryHandler::accepts(self.active_radarr_block) => {
HistoryHandler::new(self.key, self.app, self.active_radarr_block, self.context).handle()
}
_ if RootFoldersHandler::accepts(self.active_radarr_block) => {
RootFoldersHandler::new(self.key, self.app, self.active_radarr_block, self.context).handle()
}
@@ -16,10 +16,11 @@ mod tests {
#[case(0, ActiveRadarrBlock::System, ActiveRadarrBlock::Collections)]
#[case(1, ActiveRadarrBlock::Movies, ActiveRadarrBlock::Downloads)]
#[case(2, ActiveRadarrBlock::Collections, ActiveRadarrBlock::Blocklist)]
#[case(3, ActiveRadarrBlock::Downloads, ActiveRadarrBlock::RootFolders)]
#[case(4, ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Indexers)]
#[case(5, ActiveRadarrBlock::RootFolders, ActiveRadarrBlock::System)]
#[case(6, ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Movies)]
#[case(3, ActiveRadarrBlock::Downloads, ActiveRadarrBlock::History)]
#[case(4, ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::RootFolders)]
#[case(5, ActiveRadarrBlock::History, ActiveRadarrBlock::Indexers)]
#[case(6, ActiveRadarrBlock::RootFolders, ActiveRadarrBlock::System)]
#[case(7, ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Movies)]
fn test_radarr_handler_change_tab_left_right_keys(
#[case] index: usize,
#[case] left_block: ActiveRadarrBlock,
@@ -51,10 +52,11 @@ mod tests {
#[case(0, ActiveRadarrBlock::System, ActiveRadarrBlock::Collections)]
#[case(1, ActiveRadarrBlock::Movies, ActiveRadarrBlock::Downloads)]
#[case(2, ActiveRadarrBlock::Collections, ActiveRadarrBlock::Blocklist)]
#[case(3, ActiveRadarrBlock::Downloads, ActiveRadarrBlock::RootFolders)]
#[case(4, ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::Indexers)]
#[case(5, ActiveRadarrBlock::RootFolders, ActiveRadarrBlock::System)]
#[case(6, ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Movies)]
#[case(3, ActiveRadarrBlock::Downloads, ActiveRadarrBlock::History)]
#[case(4, ActiveRadarrBlock::Blocklist, ActiveRadarrBlock::RootFolders)]
#[case(5, ActiveRadarrBlock::History, ActiveRadarrBlock::Indexers)]
#[case(6, ActiveRadarrBlock::RootFolders, ActiveRadarrBlock::System)]
#[case(7, ActiveRadarrBlock::Indexers, ActiveRadarrBlock::Movies)]
fn test_radarr_handler_change_tab_left_right_keys_alt_navigation(
#[case] index: usize,
#[case] left_block: ActiveRadarrBlock,
@@ -88,9 +90,10 @@ mod tests {
#[case(1, ActiveRadarrBlock::Collections)]
#[case(2, ActiveRadarrBlock::Downloads)]
#[case(3, ActiveRadarrBlock::Blocklist)]
#[case(4, ActiveRadarrBlock::RootFolders)]
#[case(5, ActiveRadarrBlock::Indexers)]
#[case(6, ActiveRadarrBlock::System)]
#[case(4, ActiveRadarrBlock::History)]
#[case(5, ActiveRadarrBlock::RootFolders)]
#[case(6, ActiveRadarrBlock::Indexers)]
#[case(7, ActiveRadarrBlock::System)]
fn test_radarr_handler_change_tab_left_right_keys_alt_navigation_no_op_when_ignoring_quit_key(
#[case] index: usize,
#[case] block: ActiveRadarrBlock,
@@ -281,6 +284,26 @@ mod tests {
);
}
#[rstest]
fn test_delegates_history_blocks_to_history_handler(
#[values(
ActiveRadarrBlock::History,
ActiveRadarrBlock::HistoryItemDetails,
ActiveRadarrBlock::HistorySortPrompt,
ActiveRadarrBlock::FilterHistory,
ActiveRadarrBlock::FilterHistoryError,
ActiveRadarrBlock::SearchHistory,
ActiveRadarrBlock::SearchHistoryError
)]
active_radarr_block: ActiveRadarrBlock,
) {
test_handler_delegation!(
RadarrHandler,
ActiveRadarrBlock::History,
active_radarr_block
);
}
#[test]
fn test_radarr_handler_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
@@ -130,7 +130,7 @@ mod tests {
fn test_root_folders_tab_left(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.data.radarr_data.main_tabs.set_index(4);
app.data.radarr_data.main_tabs.set_index(5);
RootFoldersHandler::new(
DEFAULT_KEYBINDINGS.left.key,
@@ -142,16 +142,16 @@ mod tests {
assert_eq!(
app.data.radarr_data.main_tabs.get_active_route(),
ActiveRadarrBlock::Blocklist.into()
ActiveRadarrBlock::History.into()
);
assert_navigation_pushed!(app, ActiveRadarrBlock::Blocklist.into());
assert_navigation_pushed!(app, ActiveRadarrBlock::History.into());
}
#[rstest]
fn test_root_folders_tab_right(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.data.radarr_data.main_tabs.set_index(4);
app.data.radarr_data.main_tabs.set_index(5);
RootFoldersHandler::new(
DEFAULT_KEYBINDINGS.right.key,
@@ -26,7 +26,7 @@ mod tests {
fn test_system_tab_left(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.data.radarr_data.main_tabs.set_index(6);
app.data.radarr_data.main_tabs.set_index(7);
SystemHandler::new(
DEFAULT_KEYBINDINGS.left.key,
@@ -47,7 +47,7 @@ mod tests {
fn test_system_tab_right(#[values(true, false)] is_ready: bool) {
let mut app = App::test_default();
app.is_loading = is_ready;
app.data.radarr_data.main_tabs.set_index(6);
app.data.radarr_data.main_tabs.set_index(7);
SystemHandler::new(
DEFAULT_KEYBINDINGS.right.key,
+64
View File
@@ -408,6 +408,69 @@ pub struct SystemStatus {
pub start_time: DateTime<Utc>,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RadarrHistoryWrapper {
pub records: Vec<RadarrHistoryItem>,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RadarrHistoryData {
pub indexer: Option<String>,
pub release_group: Option<String>,
pub nzb_info_url: Option<String>,
pub download_client: Option<String>,
pub download_client_name: Option<String>,
pub age: Option<String>,
pub published_date: Option<DateTime<Utc>>,
pub message: Option<String>,
pub reason: Option<String>,
pub dropped_path: Option<String>,
pub imported_path: Option<String>,
pub source_path: Option<String>,
pub path: Option<String>,
}
#[derive(
Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq, Display, EnumDisplayStyle,
)]
#[serde(rename_all = "camelCase")]
#[strum(serialize_all = "camelCase")]
pub enum RadarrHistoryEventType {
#[default]
Unknown,
Grabbed,
#[display_style(name = "Download Folder Imported")]
DownloadFolderImported,
#[display_style(name = "Download Failed")]
DownloadFailed,
#[display_style(name = "Movie File Deleted")]
MovieFileDeleted,
#[display_style(name = "Movie Folder Imported")]
MovieFolderImported,
#[display_style(name = "Movie File Renamed")]
MovieFileRenamed,
#[display_style(name = "Download Ignored")]
DownloadIgnored,
}
#[derive(Serialize, Deserialize, Default, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RadarrHistoryItem {
#[serde(deserialize_with = "super::from_i64")]
pub id: i64,
pub source_title: HorizontallyScrollableText,
#[serde(deserialize_with = "super::from_i64")]
pub movie_id: i64,
pub quality: QualityWrapper,
pub languages: Vec<Language>,
pub date: DateTime<Utc>,
pub event_type: RadarrHistoryEventType,
#[serde(default)]
pub data: RadarrHistoryData,
}
#[derive(Default, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct RadarrTask {
@@ -461,6 +524,7 @@ serde_enum_from!(
Credits(Vec<Credit>),
DiskSpaces(Vec<DiskSpace>),
DownloadsResponse(DownloadsResponse),
HistoryWrapper(RadarrHistoryWrapper),
HostConfig(HostConfig),
Indexers(Vec<Indexer>),
IndexerSettings(IndexerSettings),
+80
View File
@@ -3,6 +3,9 @@ mod tests {
use pretty_assertions::{assert_eq, assert_str_eq};
use serde_json::json;
use crate::models::radarr_models::{
RadarrHistoryEventType, RadarrHistoryItem, RadarrHistoryWrapper,
};
use crate::models::{
Serdeable,
radarr_models::{
@@ -61,6 +64,66 @@ mod tests {
assert_str_eq!(MovieMonitor::None.to_display_str(), "None");
}
#[test]
fn test_radarr_history_event_type_display() {
assert_str_eq!(RadarrHistoryEventType::Unknown.to_string(), "unknown");
assert_str_eq!(RadarrHistoryEventType::Grabbed.to_string(), "grabbed");
assert_str_eq!(
RadarrHistoryEventType::DownloadFolderImported.to_string(),
"downloadFolderImported"
);
assert_str_eq!(
RadarrHistoryEventType::DownloadFailed.to_string(),
"downloadFailed"
);
assert_str_eq!(
RadarrHistoryEventType::MovieFileDeleted.to_string(),
"movieFileDeleted"
);
assert_str_eq!(
RadarrHistoryEventType::MovieFolderImported.to_string(),
"movieFolderImported"
);
assert_str_eq!(
RadarrHistoryEventType::MovieFileRenamed.to_string(),
"movieFileRenamed"
);
assert_str_eq!(
RadarrHistoryEventType::DownloadIgnored.to_string(),
"downloadIgnored"
);
}
#[test]
fn test_radarr_history_event_type_to_display_str() {
assert_str_eq!(RadarrHistoryEventType::Unknown.to_display_str(), "Unknown");
assert_str_eq!(RadarrHistoryEventType::Grabbed.to_display_str(), "Grabbed");
assert_str_eq!(
RadarrHistoryEventType::DownloadFolderImported.to_display_str(),
"Download Folder Imported"
);
assert_str_eq!(
RadarrHistoryEventType::DownloadFailed.to_display_str(),
"Download Failed"
);
assert_str_eq!(
RadarrHistoryEventType::MovieFileDeleted.to_display_str(),
"Movie File Deleted"
);
assert_str_eq!(
RadarrHistoryEventType::MovieFolderImported.to_display_str(),
"Movie Folder Imported"
);
assert_str_eq!(
RadarrHistoryEventType::MovieFileRenamed.to_display_str(),
"Movie File Renamed"
);
assert_str_eq!(
RadarrHistoryEventType::DownloadIgnored.to_display_str(),
"Download Ignored"
);
}
#[test]
fn test_download_record_default_indexer_value() {
let json = r#"{
@@ -235,6 +298,23 @@ mod tests {
);
}
#[test]
fn test_radarr_serdeable_from_history_wrapper() {
let history_wrapper = RadarrHistoryWrapper {
records: vec![RadarrHistoryItem {
id: 1,
..RadarrHistoryItem::default()
}],
};
let radarr_serdeable: RadarrSerdeable = history_wrapper.clone().into();
assert_eq!(
radarr_serdeable,
RadarrSerdeable::HistoryWrapper(history_wrapper)
);
}
#[test]
fn test_radarr_serdeable_from_log_response() {
let log_response = LogResponse {
+4
View File
@@ -15,6 +15,7 @@ use crate::models::{HorizontallyScrollableText, ScrollableText};
mod modals_tests;
#[derive(Default)]
#[cfg_attr(test, derive(Debug))]
pub struct MovieDetailsModal {
pub movie_details: ScrollableText,
pub file_details: String,
@@ -97,6 +98,7 @@ impl From<&RadarrData<'_>> for EditIndexerModal {
}
#[derive(Default)]
#[cfg_attr(test, derive(Debug))]
pub struct EditMovieModal {
pub minimum_availability_list: StatefulList<MinimumAvailability>,
pub quality_profile_list: StatefulList<String>,
@@ -157,6 +159,7 @@ impl From<&RadarrData<'_>> for EditMovieModal {
}
#[derive(Default)]
#[cfg_attr(test, derive(Debug))]
pub struct AddMovieModal {
pub root_folder_list: StatefulList<RootFolder>,
pub monitor_list: StatefulList<MovieMonitor>,
@@ -186,6 +189,7 @@ impl From<&RadarrData<'_>> for AddMovieModal {
}
#[derive(Default)]
#[cfg_attr(test, derive(Debug))]
pub struct EditCollectionModal {
pub monitored: Option<bool>,
pub minimum_availability_list: StatefulList<MinimumAvailability>,
+32 -3
View File
@@ -1,5 +1,5 @@
use crate::app::context_clues::{
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
};
use crate::app::radarr::radarr_context_clues::{
@@ -8,7 +8,7 @@ use crate::app::radarr::radarr_context_clues::{
};
use crate::models::radarr_models::{
AddMovieSearchResult, BlocklistItem, Collection, CollectionMovie, DownloadRecord,
IndexerSettings, Movie, RadarrTask,
IndexerSettings, Movie, RadarrHistoryItem, RadarrTask,
};
use crate::models::servarr_data::modals::{EditIndexerModal, IndexerTestResultModalItem};
use crate::models::servarr_data::radarr::modals::{
@@ -34,7 +34,8 @@ use {
crate::network::radarr_network::radarr_network_test_utils::test_utils::{
add_movie_search_result, blocklist_item, cast_credit, collection, collection_movie,
crew_credit, download_record, indexer, log_line, movie, movie_history_item,
quality_profile_map, tags_map, task, torrent_release, updates, usenet_release,
quality_profile_map, radarr_history_item, tags_map, task, torrent_release, updates,
usenet_release,
},
crate::network::servarr_test_utils::diskspace,
crate::network::servarr_test_utils::indexer_test_result,
@@ -62,6 +63,7 @@ pub struct RadarrData<'a> {
pub downloads: StatefulTable<DownloadRecord>,
pub indexers: StatefulTable<Indexer>,
pub blocklist: StatefulTable<BlocklistItem>,
pub history: StatefulTable<RadarrHistoryItem>,
pub quality_profile_map: BiMap<i64, String>,
pub tags_map: BiMap<i64, String>,
pub collections: StatefulTable<Collection>,
@@ -135,6 +137,7 @@ impl<'a> Default for RadarrData<'a> {
downloads: StatefulTable::default(),
indexers: StatefulTable::default(),
blocklist: StatefulTable::default(),
history: StatefulTable::default(),
quality_profile_map: BiMap::default(),
tags_map: BiMap::default(),
collections: StatefulTable::default(),
@@ -184,6 +187,12 @@ impl<'a> Default for RadarrData<'a> {
contextual_help: Some(&BLOCKLIST_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "History".to_string(),
route: ActiveRadarrBlock::History.into(),
contextual_help: Some(&HISTORY_CONTEXT_CLUES),
config: None,
},
TabRoute {
title: "Root Folders".to_string(),
route: ActiveRadarrBlock::RootFolders.into(),
@@ -387,6 +396,10 @@ impl RadarrData<'_> {
radarr_data.downloads.set_items(vec![download_record()]);
radarr_data.blocklist.set_items(vec![blocklist_item()]);
radarr_data.blocklist.sorting(vec![sort_option!(id)]);
radarr_data.history.set_items(vec![radarr_history_item()]);
radarr_data.history.sorting(vec![sort_option!(id)]);
radarr_data.history.search = Some("Something".into());
radarr_data.history.filter = Some("Something".into());
radarr_data.indexers.set_items(vec![indexer()]);
radarr_data.indexers.sorting(vec![sort_option!(id)]);
radarr_data.indexers.search = Some("Something".into());
@@ -420,6 +433,13 @@ pub enum ActiveRadarrBlock {
BlocklistClearAllItemsPrompt,
BlocklistItemDetails,
BlocklistSortPrompt,
History,
HistoryItemDetails,
HistorySortPrompt,
FilterHistory,
FilterHistoryError,
SearchHistory,
SearchHistoryError,
Collections,
CollectionsSortPrompt,
CollectionDetails,
@@ -538,6 +558,15 @@ pub static BLOCKLIST_BLOCKS: [ActiveRadarrBlock; 5] = [
ActiveRadarrBlock::BlocklistClearAllItemsPrompt,
ActiveRadarrBlock::BlocklistSortPrompt,
];
pub static HISTORY_BLOCKS: [ActiveRadarrBlock; 7] = [
ActiveRadarrBlock::History,
ActiveRadarrBlock::HistoryItemDetails,
ActiveRadarrBlock::HistorySortPrompt,
ActiveRadarrBlock::FilterHistory,
ActiveRadarrBlock::FilterHistoryError,
ActiveRadarrBlock::SearchHistory,
ActiveRadarrBlock::SearchHistoryError,
];
pub static ADD_MOVIE_BLOCKS: [ActiveRadarrBlock; 10] = [
ActiveRadarrBlock::AddMovieSearchInput,
ActiveRadarrBlock::AddMovieSearchResults,
@@ -2,8 +2,8 @@
mod tests {
mod radarr_data_tests {
use crate::app::context_clues::{
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, INDEXERS_CONTEXT_CLUES,
ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
BLOCKLIST_CONTEXT_CLUES, DOWNLOADS_CONTEXT_CLUES, HISTORY_CONTEXT_CLUES,
INDEXERS_CONTEXT_CLUES, ROOT_FOLDERS_CONTEXT_CLUES, SYSTEM_CONTEXT_CLUES,
};
use crate::app::radarr::radarr_context_clues::{
COLLECTIONS_CONTEXT_CLUES, LIBRARY_CONTEXT_CLUES, MANUAL_MOVIE_SEARCH_CONTEXT_CLUES,
@@ -105,41 +105,42 @@ mod tests {
fn test_radarr_data_defaults() {
let radarr_data = RadarrData::default();
assert!(radarr_data.root_folders.items.is_empty());
assert_is_empty!(radarr_data.root_folders.items);
assert_eq!(radarr_data.disk_space_vec, Vec::new());
assert!(radarr_data.version.is_empty());
assert_is_empty!(radarr_data.version);
assert_eq!(radarr_data.start_time, <DateTime<Utc>>::default());
assert!(radarr_data.movies.is_empty());
assert_is_empty!(radarr_data.movies);
assert_eq!(radarr_data.selected_block, BlockSelectionState::default());
assert!(radarr_data.downloads.items.is_empty());
assert!(radarr_data.indexers.items.is_empty());
assert!(radarr_data.blocklist.items.is_empty());
assert!(radarr_data.quality_profile_map.is_empty());
assert!(radarr_data.tags_map.is_empty());
assert!(radarr_data.collections.items.is_empty());
assert!(radarr_data.collection_movies.items.is_empty());
assert!(radarr_data.logs.items.is_empty());
assert!(radarr_data.log_details.items.is_empty());
assert!(radarr_data.tasks.items.is_empty());
assert!(radarr_data.queued_events.items.is_empty());
assert!(radarr_data.updates.get_text().is_empty());
assert!(radarr_data.add_movie_search.is_none());
assert!(radarr_data.add_movie_modal.is_none());
assert!(radarr_data.add_searched_movies.is_none());
assert!(radarr_data.edit_movie_modal.is_none());
assert!(radarr_data.edit_collection_modal.is_none());
assert!(radarr_data.edit_root_folder.is_none());
assert!(radarr_data.edit_indexer_modal.is_none());
assert!(radarr_data.indexer_settings.is_none());
assert!(radarr_data.indexer_test_errors.is_none());
assert!(radarr_data.indexer_test_all_results.is_none());
assert!(radarr_data.movie_details_modal.is_none());
assert!(radarr_data.prompt_confirm_action.is_none());
assert_is_empty!(radarr_data.downloads.items);
assert_is_empty!(radarr_data.indexers.items);
assert_is_empty!(radarr_data.blocklist.items);
assert_is_empty!(radarr_data.history.items);
assert_is_empty!(radarr_data.quality_profile_map);
assert_is_empty!(radarr_data.tags_map);
assert_is_empty!(radarr_data.collections.items);
assert_is_empty!(radarr_data.collection_movies.items);
assert_is_empty!(radarr_data.logs.items);
assert_is_empty!(radarr_data.log_details.items);
assert_is_empty!(radarr_data.tasks.items);
assert_is_empty!(radarr_data.queued_events.items);
assert_is_empty!(radarr_data.updates.get_text());
assert_none!(&radarr_data.add_movie_search);
assert_none!(&radarr_data.add_movie_modal);
assert_none!(&radarr_data.add_searched_movies);
assert_none!(&radarr_data.edit_movie_modal);
assert_none!(&radarr_data.edit_collection_modal);
assert_none!(&radarr_data.edit_root_folder);
assert_none!(&radarr_data.edit_indexer_modal);
assert_none!(&radarr_data.indexer_settings);
assert_none!(&radarr_data.indexer_test_errors);
assert_none!(&radarr_data.indexer_test_all_results);
assert_none!(&radarr_data.movie_details_modal);
assert_none!(&radarr_data.prompt_confirm_action);
assert!(!radarr_data.prompt_confirm);
assert!(!radarr_data.delete_movie_files);
assert!(!radarr_data.add_list_exclusion);
assert_eq!(radarr_data.main_tabs.tabs.len(), 7);
assert_eq!(radarr_data.main_tabs.tabs.len(), 8);
assert_str_eq!(radarr_data.main_tabs.tabs[0].title, "Library");
assert_eq!(
@@ -189,42 +190,54 @@ mod tests {
);
assert_eq!(radarr_data.main_tabs.tabs[3].config, None);
assert_str_eq!(radarr_data.main_tabs.tabs[4].title, "Root Folders");
assert_str_eq!(radarr_data.main_tabs.tabs[4].title, "History");
assert_eq!(
radarr_data.main_tabs.tabs[4].route,
ActiveRadarrBlock::RootFolders.into()
ActiveRadarrBlock::History.into()
);
assert!(radarr_data.main_tabs.tabs[4].contextual_help.is_some());
assert_eq!(
radarr_data.main_tabs.tabs[4].contextual_help.unwrap(),
&ROOT_FOLDERS_CONTEXT_CLUES
&HISTORY_CONTEXT_CLUES
);
assert_eq!(radarr_data.main_tabs.tabs[4].config, None);
assert_str_eq!(radarr_data.main_tabs.tabs[5].title, "Indexers");
assert_str_eq!(radarr_data.main_tabs.tabs[5].title, "Root Folders");
assert_eq!(
radarr_data.main_tabs.tabs[5].route,
ActiveRadarrBlock::Indexers.into()
ActiveRadarrBlock::RootFolders.into()
);
assert!(radarr_data.main_tabs.tabs[5].contextual_help.is_some());
assert_eq!(
radarr_data.main_tabs.tabs[5].contextual_help.unwrap(),
&INDEXERS_CONTEXT_CLUES
&ROOT_FOLDERS_CONTEXT_CLUES
);
assert_eq!(radarr_data.main_tabs.tabs[5].config, None);
assert_str_eq!(radarr_data.main_tabs.tabs[6].title, "System");
assert_str_eq!(radarr_data.main_tabs.tabs[6].title, "Indexers");
assert_eq!(
radarr_data.main_tabs.tabs[6].route,
ActiveRadarrBlock::System.into()
ActiveRadarrBlock::Indexers.into()
);
assert!(radarr_data.main_tabs.tabs[6].contextual_help.is_some());
assert_eq!(
radarr_data.main_tabs.tabs[6].contextual_help.unwrap(),
&SYSTEM_CONTEXT_CLUES
&INDEXERS_CONTEXT_CLUES
);
assert_eq!(radarr_data.main_tabs.tabs[6].config, None);
assert_str_eq!(radarr_data.main_tabs.tabs[7].title, "System");
assert_eq!(
radarr_data.main_tabs.tabs[7].route,
ActiveRadarrBlock::System.into()
);
assert!(radarr_data.main_tabs.tabs[7].contextual_help.is_some());
assert_eq!(
radarr_data.main_tabs.tabs[7].contextual_help.unwrap(),
&SYSTEM_CONTEXT_CLUES
);
assert_eq!(radarr_data.main_tabs.tabs[7].config, None);
assert_eq!(radarr_data.movie_info_tabs.tabs.len(), 6);
assert_str_eq!(radarr_data.movie_info_tabs.tabs[0].title, "Details");
@@ -334,8 +347,8 @@ mod tests {
DELETE_MOVIE_SELECTION_BLOCKS, DOWNLOADS_BLOCKS, EDIT_COLLECTION_BLOCKS,
EDIT_COLLECTION_SELECTION_BLOCKS, EDIT_INDEXER_BLOCKS, EDIT_INDEXER_NZB_SELECTION_BLOCKS,
EDIT_INDEXER_TORRENT_SELECTION_BLOCKS, EDIT_MOVIE_BLOCKS, EDIT_MOVIE_SELECTION_BLOCKS,
INDEXER_SETTINGS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, INDEXERS_BLOCKS, LIBRARY_BLOCKS,
MOVIE_DETAILS_BLOCKS, ROOT_FOLDERS_BLOCKS, SYSTEM_DETAILS_BLOCKS,
HISTORY_BLOCKS, INDEXER_SETTINGS_BLOCKS, INDEXER_SETTINGS_SELECTION_BLOCKS, INDEXERS_BLOCKS,
LIBRARY_BLOCKS, MOVIE_DETAILS_BLOCKS, ROOT_FOLDERS_BLOCKS, SYSTEM_DETAILS_BLOCKS,
};
#[test]
@@ -388,6 +401,18 @@ mod tests {
assert!(BLOCKLIST_BLOCKS.contains(&ActiveRadarrBlock::BlocklistSortPrompt));
}
#[test]
fn test_history_blocks_contents() {
assert_eq!(HISTORY_BLOCKS.len(), 7);
assert!(HISTORY_BLOCKS.contains(&ActiveRadarrBlock::History));
assert!(HISTORY_BLOCKS.contains(&ActiveRadarrBlock::HistoryItemDetails));
assert!(HISTORY_BLOCKS.contains(&ActiveRadarrBlock::HistorySortPrompt));
assert!(HISTORY_BLOCKS.contains(&ActiveRadarrBlock::FilterHistory));
assert!(HISTORY_BLOCKS.contains(&ActiveRadarrBlock::FilterHistoryError));
assert!(HISTORY_BLOCKS.contains(&ActiveRadarrBlock::SearchHistory));
assert!(HISTORY_BLOCKS.contains(&ActiveRadarrBlock::SearchHistoryError));
}
#[test]
fn test_add_movie_blocks_contents() {
assert_eq!(ADD_MOVIE_BLOCKS.len(), 10);
+63
View File
@@ -0,0 +1,63 @@
use crate::models::Route;
use crate::models::radarr_models::RadarrHistoryWrapper;
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::network::radarr_network::RadarrEvent;
use crate::network::{Network, RequestMethod};
use anyhow::Result;
use log::info;
use serde_json::Value;
#[cfg(test)]
#[path = "radarr_history_network_tests.rs"]
mod radarr_history_network_tests;
impl Network<'_, '_> {
pub(in crate::network::radarr_network) async fn get_radarr_history(
&mut self,
events: u64,
) -> Result<RadarrHistoryWrapper> {
info!("Fetching all Radarr history events");
let event = RadarrEvent::GetHistory(events);
let params = format!("pageSize={events}&sortDirection=descending&sortKey=date");
let request_props = self
.request_props_from(event, RequestMethod::Get, None::<()>, None, Some(params))
.await;
self
.handle_request::<(), RadarrHistoryWrapper>(request_props, |history_response, mut app| {
if !matches!(
app.get_current_route(),
Route::Radarr(ActiveRadarrBlock::HistorySortPrompt, _)
) {
let mut history_vec = history_response.records;
history_vec.sort_by(|a, b| a.id.cmp(&b.id));
app.data.radarr_data.history.set_items(history_vec);
app.data.radarr_data.history.apply_sorting_toggle(false);
}
})
.await
}
pub(in crate::network::radarr_network) async fn mark_radarr_history_item_as_failed(
&mut self,
history_item_id: i64,
) -> Result<Value> {
info!("Marking the Radarr history item with ID: {history_item_id} as 'failed'");
let event = RadarrEvent::MarkHistoryItemAsFailed(history_item_id);
let request_props = self
.request_props_from(
event,
RequestMethod::Post,
None,
Some(format!("/{history_item_id}")),
None,
)
.await;
self
.handle_request::<(), Value>(request_props, |_, _| ())
.await
}
}
@@ -0,0 +1,196 @@
#[cfg(test)]
mod tests {
use crate::models::radarr_models::{RadarrHistoryItem, RadarrHistoryWrapper, RadarrSerdeable};
use crate::models::servarr_data::radarr::radarr_data::ActiveRadarrBlock;
use crate::models::stateful_table::SortOption;
use crate::network::network_tests::test_utils::{MockServarrApi, test_network};
use crate::network::radarr_network::RadarrEvent;
use crate::network::radarr_network::radarr_network_test_utils::test_utils::radarr_history_item;
use pretty_assertions::assert_eq;
use rstest::rstest;
use serde_json::json;
#[rstest]
#[tokio::test]
async fn test_handle_get_radarr_history_event(#[values(true, false)] use_custom_sorting: bool) {
let history_json = json!({"records": [{
"id": 123,
"sourceTitle": "z movie",
"movieId": 1007,
"quality": { "quality": { "name": "HD - 1080p" } },
"languages": [{ "id": 1, "name": "English" }],
"date": "2022-12-30T07:37:56Z",
"eventType": "grabbed",
"data": {
"indexer": "DrunkenSlug (Prowlarr)",
"releaseGroup": "SPARKS",
"downloadClient": "transmission",
}
},
{
"id": 456,
"sourceTitle": "A Movie",
"movieId": 2001,
"quality": { "quality": { "name": "HD - 1080p" } },
"languages": [{ "id": 1, "name": "English" }],
"date": "2022-12-30T07:37:56Z",
"eventType": "grabbed",
"data": {
"indexer": "DrunkenSlug (Prowlarr)",
"releaseGroup": "SPARKS",
"downloadClient": "transmission",
}
}]});
let response: RadarrHistoryWrapper = serde_json::from_value(history_json.clone()).unwrap();
let mut expected_history_items = vec![
RadarrHistoryItem {
id: 123,
movie_id: 1007,
source_title: "z movie".into(),
..radarr_history_item()
},
RadarrHistoryItem {
id: 456,
movie_id: 2001,
source_title: "A Movie".into(),
..radarr_history_item()
},
];
let (mock, app, _server) = MockServarrApi::get()
.returns(history_json)
.query("pageSize=500&sortDirection=descending&sortKey=date")
.build_for(RadarrEvent::GetHistory(500))
.await;
app.lock().await.data.radarr_data.history.sort_asc = true;
if use_custom_sorting {
let cmp_fn = |a: &RadarrHistoryItem, b: &RadarrHistoryItem| {
a.source_title
.text
.to_lowercase()
.cmp(&b.source_title.text.to_lowercase())
};
expected_history_items.sort_by(cmp_fn);
let history_sort_option = SortOption {
name: "Source Title",
cmp_fn: Some(cmp_fn),
};
app
.lock()
.await
.data
.radarr_data
.history
.sorting(vec![history_sort_option]);
}
let mut network = test_network(&app);
let RadarrSerdeable::HistoryWrapper(history) = network
.handle_radarr_event(RadarrEvent::GetHistory(500))
.await
.unwrap()
else {
panic!("Expected HistoryWrapper")
};
mock.assert_async().await;
assert_eq!(
app.lock().await.data.radarr_data.history.items,
expected_history_items
);
assert!(app.lock().await.data.radarr_data.history.sort_asc);
assert_eq!(history, response);
}
#[tokio::test]
async fn test_handle_get_radarr_history_event_no_op_when_user_is_selecting_sort_options() {
let history_json = json!({"records": [{
"id": 123,
"sourceTitle": "z movie",
"movieId": 1007,
"quality": { "quality": { "name": "Bluray-1080p" } },
"languages": [{ "id": 1, "name": "English" }],
"date": "2024-02-10T07:28:45Z",
"eventType": "grabbed",
"data": {
"indexer": "DrunkenSlug (Prowlarr)",
"releaseGroup": "SPARKS"
}
},
{
"id": 456,
"sourceTitle": "A Movie",
"movieId": 2001,
"quality": { "quality": { "name": "Bluray-1080p" } },
"languages": [{ "id": 1, "name": "English" }],
"date": "2024-02-10T07:28:45Z",
"eventType": "grabbed",
"data": {
"indexer": "DrunkenSlug (Prowlarr)",
"releaseGroup": "SPARKS"
}
}]});
let response: RadarrHistoryWrapper = serde_json::from_value(history_json.clone()).unwrap();
let (mock, app, _server) = MockServarrApi::get()
.returns(history_json)
.query("pageSize=500&sortDirection=descending&sortKey=date")
.build_for(RadarrEvent::GetHistory(500))
.await;
app.lock().await.data.radarr_data.history.sort_asc = true;
app
.lock()
.await
.push_navigation_stack(ActiveRadarrBlock::HistorySortPrompt.into());
let cmp_fn = |a: &RadarrHistoryItem, b: &RadarrHistoryItem| {
a.source_title
.text
.to_lowercase()
.cmp(&b.source_title.text.to_lowercase())
};
let history_sort_option = SortOption {
name: "Source Title",
cmp_fn: Some(cmp_fn),
};
app
.lock()
.await
.data
.radarr_data
.history
.sorting(vec![history_sort_option]);
let mut network = test_network(&app);
let RadarrSerdeable::HistoryWrapper(history) = network
.handle_radarr_event(RadarrEvent::GetHistory(500))
.await
.unwrap()
else {
panic!("Expected HistoryWrapper")
};
mock.assert_async().await;
assert_is_empty!(app.lock().await.data.radarr_data.history);
assert!(app.lock().await.data.radarr_data.history.sort_asc);
assert_eq!(history, response);
}
#[tokio::test]
async fn test_handle_mark_radarr_history_item_as_failed_event() {
let expected_history_item_id = 1;
let (mock, app, _server) = MockServarrApi::post()
.returns(json!({}))
.path("/1")
.build_for(RadarrEvent::MarkHistoryItemAsFailed(
expected_history_item_id,
))
.await;
let mut network = test_network(&app);
let result = network
.handle_radarr_event(RadarrEvent::MarkHistoryItemAsFailed(
expected_history_item_id,
))
.await;
mock.assert_async().await;
assert_ok!(result);
}
}
+13
View File
@@ -16,6 +16,7 @@ use super::NetworkResource;
mod blocklist;
mod collections;
mod downloads;
mod history;
mod indexers;
mod library;
mod root_folders;
@@ -47,10 +48,12 @@ pub enum RadarrEvent {
GetBlocklist,
GetCollections,
GetDownloads(u64),
GetHistory(u64),
GetHostConfig,
GetIndexers,
GetAllIndexerSettings,
GetLogs(u64),
MarkHistoryItemAsFailed(i64),
GetMovieCredits(i64),
GetMovieDetails(i64),
GetMovieHistory(i64),
@@ -86,7 +89,9 @@ impl NetworkResource for RadarrEvent {
RadarrEvent::GetBlocklist => "/blocklist?page=1&pageSize=10000",
RadarrEvent::GetCollections | RadarrEvent::EditCollection(_) => "/collection",
RadarrEvent::GetDownloads(_) | RadarrEvent::DeleteDownload(_) => "/queue",
RadarrEvent::GetHistory(_) => "/history",
RadarrEvent::GetHostConfig | RadarrEvent::GetSecurityConfig => "/config/host",
RadarrEvent::MarkHistoryItemAsFailed(_) => "/history/failed",
RadarrEvent::GetIndexers | RadarrEvent::EditIndexer(_) | RadarrEvent::DeleteIndexer(_) => {
"/indexer"
}
@@ -199,6 +204,10 @@ impl Network<'_, '_> {
.get_radarr_downloads(count)
.await
.map(RadarrSerdeable::from),
RadarrEvent::GetHistory(events) => self
.get_radarr_history(events)
.await
.map(RadarrSerdeable::from),
RadarrEvent::GetHostConfig => self
.get_radarr_host_config()
.await
@@ -208,6 +217,10 @@ impl Network<'_, '_> {
.get_radarr_logs(events)
.await
.map(RadarrSerdeable::from),
RadarrEvent::MarkHistoryItemAsFailed(history_item_id) => self
.mark_radarr_history_item_as_failed(history_item_id)
.await
.map(RadarrSerdeable::from),
RadarrEvent::GetMovieCredits(movie_id) => {
self.get_credits(movie_id).await.map(RadarrSerdeable::from)
}
@@ -3,8 +3,8 @@ pub mod test_utils {
use crate::models::radarr_models::{
AddMovieSearchResult, BlocklistItem, BlocklistItemMovie, Collection, CollectionMovie, Credit,
CreditType, DownloadRecord, DownloadsResponse, IndexerSettings, MediaInfo, MinimumAvailability,
Movie, MovieCollection, MovieFile, MovieHistoryItem, RadarrRelease, RadarrTask, RadarrTaskName,
Rating, RatingsList,
Movie, MovieCollection, MovieFile, MovieHistoryItem, RadarrHistoryData, RadarrHistoryEventType,
RadarrHistoryItem, RadarrRelease, RadarrTask, RadarrTaskName, Rating, RatingsList,
};
use crate::models::servarr_models::{
Indexer, IndexerField, Language, Quality, QualityWrapper, RootFolder,
@@ -313,6 +313,24 @@ pub mod test_utils {
}
}
pub fn radarr_history_item() -> RadarrHistoryItem {
RadarrHistoryItem {
id: 1,
source_title: HorizontallyScrollableText::from("Test"),
movie_id: 1,
quality: quality_wrapper(),
languages: vec![language()],
date: DateTime::from(DateTime::parse_from_rfc3339("2022-12-30T07:37:56Z").unwrap()),
event_type: RadarrHistoryEventType::Grabbed,
data: RadarrHistoryData {
indexer: Some("DrunkenSlug (Prowlarr)".to_owned()),
release_group: Some("SPARKS".to_owned()),
download_client: Some("transmission".to_owned()),
..RadarrHistoryData::default()
},
}
}
pub fn download_record() -> DownloadRecord {
DownloadRecord {
title: "Test Download Title".to_owned(),
@@ -136,7 +136,9 @@ mod test {
#[case(RadarrEvent::ClearBlocklist, "/blocklist/bulk")]
#[case(RadarrEvent::DeleteBlocklistItem(1), "/blocklist")]
#[case(RadarrEvent::GetBlocklist, "/blocklist?page=1&pageSize=10000")]
#[case(RadarrEvent::GetHistory(500), "/history")]
#[case(RadarrEvent::GetLogs(500), "/log")]
#[case(RadarrEvent::MarkHistoryItemAsFailed(1), "/history/failed")]
#[case(RadarrEvent::SearchNewMovie(String::new()), "/movie/lookup")]
#[case(RadarrEvent::GetMovieCredits(0), "/credit")]
#[case(RadarrEvent::GetMovieHistory(0), "/history/movie")]
@@ -0,0 +1,79 @@
#[cfg(test)]
mod tests {
use strum::IntoEnumIterator;
use crate::app::App;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, HISTORY_BLOCKS};
use crate::ui::DrawUi;
use crate::ui::radarr_ui::history::HistoryUi;
use crate::ui::ui_test_utils::test_utils::render_to_string_with_app;
#[test]
fn test_history_ui_accepts() {
ActiveRadarrBlock::iter().for_each(|active_radarr_block| {
if HISTORY_BLOCKS.contains(&active_radarr_block) {
assert!(HistoryUi::accepts(active_radarr_block.into()));
} else {
assert!(!HistoryUi::accepts(active_radarr_block.into()));
}
});
}
mod snapshot_tests {
use crate::ui::ui_test_utils::test_utils::TerminalSize;
use rstest::rstest;
use super::*;
#[test]
fn test_history_ui_renders_loading() {
let mut app = App::test_default();
app.is_loading = true;
app.push_navigation_stack(ActiveRadarrBlock::History.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
HistoryUi::draw(f, app, f.area());
});
insta::assert_snapshot!(output);
}
#[rstest]
fn test_history_ui_renders_empty(
#[values(ActiveRadarrBlock::History, ActiveRadarrBlock::HistoryItemDetails)]
active_radarr_block: ActiveRadarrBlock,
) {
let mut app = App::test_default();
app.push_navigation_stack(active_radarr_block.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
HistoryUi::draw(f, app, f.area());
});
insta::assert_snapshot!(format!("loading_history_tab_{active_radarr_block}"), output);
}
#[rstest]
fn test_history_ui_renders(
#[values(
ActiveRadarrBlock::History,
ActiveRadarrBlock::HistoryItemDetails,
ActiveRadarrBlock::HistorySortPrompt,
ActiveRadarrBlock::FilterHistory,
ActiveRadarrBlock::FilterHistoryError,
ActiveRadarrBlock::SearchHistory,
ActiveRadarrBlock::SearchHistoryError
)]
active_radarr_block: ActiveRadarrBlock,
) {
let mut app = App::test_default_fully_populated();
app.push_navigation_stack(active_radarr_block.into());
let output = render_to_string_with_app(TerminalSize::Large, &mut app, |f, app| {
HistoryUi::draw(f, app, f.area());
});
insta::assert_snapshot!(format!("history_tab_{active_radarr_block}"), output);
}
}
}
+129
View File
@@ -0,0 +1,129 @@
use crate::app::App;
use crate::models::Route;
use crate::models::radarr_models::RadarrHistoryItem;
use crate::models::servarr_data::radarr::radarr_data::{ActiveRadarrBlock, HISTORY_BLOCKS};
use crate::ui::DrawUi;
use crate::ui::styles::{ManagarrStyle, secondary_style};
use crate::ui::utils::{get_width_from_percentage, layout_block_top_border};
use crate::ui::widgets::managarr_table::ManagarrTable;
use crate::ui::widgets::message::Message;
use crate::ui::widgets::popup::{Popup, Size};
use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Rect};
use ratatui::text::Text;
use ratatui::widgets::{Cell, Row};
use super::radarr_ui_utils::create_history_event_details;
#[cfg(test)]
#[path = "history_ui_tests.rs"]
mod history_ui_tests;
pub(super) struct HistoryUi;
impl DrawUi for HistoryUi {
fn accepts(route: Route) -> bool {
if let Route::Radarr(active_radarr_block, _) = route {
return HISTORY_BLOCKS.contains(&active_radarr_block);
}
false
}
fn draw(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
if let Route::Radarr(active_radarr_block, _) = app.get_current_route() {
draw_history_table(f, app, area);
if active_radarr_block == ActiveRadarrBlock::HistoryItemDetails {
draw_history_item_details_popup(f, app);
}
}
}
}
fn draw_history_table(f: &mut Frame<'_>, app: &mut App<'_>, area: Rect) {
let current_selection = if app.data.radarr_data.history.items.is_empty() {
RadarrHistoryItem::default()
} else {
app.data.radarr_data.history.current_selection().clone()
};
if let Route::Radarr(active_radarr_block, _) = app.get_current_route() {
let history_row_mapping = |history_item: &RadarrHistoryItem| {
let RadarrHistoryItem {
source_title,
languages,
quality,
event_type,
date,
..
} = history_item;
source_title.scroll_left_or_reset(
get_width_from_percentage(area, 40),
current_selection == *history_item,
app.ui_scroll_tick_count == 0,
);
Row::new(vec![
Cell::from(source_title.to_string()),
Cell::from(event_type.to_string()),
Cell::from(
languages
.iter()
.map(|language| language.name.to_owned())
.collect::<Vec<String>>()
.join(","),
),
Cell::from(quality.quality.name.to_owned()),
Cell::from(date.to_string()),
])
.primary()
};
let history_table =
ManagarrTable::new(Some(&mut app.data.radarr_data.history), history_row_mapping)
.block(layout_block_top_border())
.loading(app.is_loading)
.sorting(active_radarr_block == ActiveRadarrBlock::HistorySortPrompt)
.searching(active_radarr_block == ActiveRadarrBlock::SearchHistory)
.search_produced_empty_results(active_radarr_block == ActiveRadarrBlock::SearchHistoryError)
.filtering(active_radarr_block == ActiveRadarrBlock::FilterHistory)
.filter_produced_empty_results(active_radarr_block == ActiveRadarrBlock::FilterHistoryError)
.headers(["Source Title", "Event Type", "Language", "Quality", "Date"])
.constraints([
Constraint::Percentage(40),
Constraint::Percentage(15),
Constraint::Percentage(12),
Constraint::Percentage(13),
Constraint::Percentage(20),
]);
if [
ActiveRadarrBlock::SearchHistory,
ActiveRadarrBlock::FilterHistory,
]
.contains(&active_radarr_block)
{
history_table.show_cursor(f, area);
}
f.render_widget(history_table, area);
}
}
fn draw_history_item_details_popup(f: &mut Frame<'_>, app: &mut App<'_>) {
let current_selection = if app.data.radarr_data.history.items.is_empty() {
RadarrHistoryItem::default()
} else {
app.data.radarr_data.history.current_selection().clone()
};
let line_vec = create_history_event_details(current_selection);
let text = Text::from(line_vec);
let message = Message::new(text)
.title("Details")
.style(secondary_style())
.alignment(Alignment::Left);
f.render_widget(Popup::new(message).size(Size::NarrowLongMessage), f.area());
}
@@ -0,0 +1,28 @@
---
source: src/ui/radarr_ui/history/history_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Source Title ▼ Event Type Language Quality Date
=> Test grabbed English HD - 1080p 2022-12-30 07:37:56 UTC
╭───────────────── Filter ──────────────────╮
│Something │
╰─────────────────────────────────────────────╯
@@ -0,0 +1,31 @@
---
source: src/ui/radarr_ui/history/history_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Source Title ▼ Event Type Language Quality Date
=> Test grabbed English HD - 1080p 2022-12-30 07:37:56 UTC
╭─────────────── Error ───────────────╮
│The given filter produced empty results│
│ │
╰───────────────────────────────────────╯
@@ -0,0 +1,7 @@
---
source: src/ui/radarr_ui/history/history_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Source Title ▼ Event Type Language Quality Date
=> Test grabbed English HD - 1080p 2022-12-30 07:37:56 UTC
@@ -0,0 +1,39 @@
---
source: src/ui/radarr_ui/history/history_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Source Title ▼ Event Type Language Quality Date
=> Test grabbed English HD - 1080p 2022-12-30 07:37:56 UTC
╭─────────────────────────────────── Details ───────────────────────────────────╮
│Source Title: Test │
│Event Type: grabbed │
│Indexer: DrunkenSlug (Prowlarr) │
│Release Group: SPARKS │
│NZB Info URL: │
│Download Client: transmission │
│Age: 0 days │
│Published Date: 1970-01-01 00:00:00 UTC │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰─────────────────────────────────────────────────────────────────────────────────╯
@@ -0,0 +1,42 @@
---
source: src/ui/radarr_ui/history/history_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Source Title Event Type Language Quality Date
=> Test grabbed English HD - 1080p 2022-12-30 07:37:56 UTC
╭───────────────────────────────╮
│Something │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────╯
@@ -0,0 +1,28 @@
---
source: src/ui/radarr_ui/history/history_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Source Title ▼ Event Type Language Quality Date
=> Test grabbed English HD - 1080p 2022-12-30 07:37:56 UTC
╭───────────────── Search ──────────────────╮
│Something │
╰─────────────────────────────────────────────╯
@@ -0,0 +1,31 @@
---
source: src/ui/radarr_ui/history/history_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Source Title ▼ Event Type Language Quality Date
=> Test grabbed English HD - 1080p 2022-12-30 07:37:56 UTC
╭─────────────── Error ───────────────╮
│ No items found matching search │
│ │
╰───────────────────────────────────────╯
@@ -0,0 +1,8 @@
---
source: src/ui/radarr_ui/history/history_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Loading ...
@@ -0,0 +1,5 @@
---
source: src/ui/radarr_ui/history/history_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
@@ -0,0 +1,39 @@
---
source: src/ui/radarr_ui/history/history_ui_tests.rs
expression: output
---
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
╭─────────────────────────────────── Details ───────────────────────────────────╮
│Source Title: │
│Event Type: unknown │
│ │
│No additional data available │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰─────────────────────────────────────────────────────────────────────────────────╯
+4
View File
@@ -21,6 +21,7 @@ use crate::ui::draw_tabs;
use crate::ui::radarr_ui::blocklist::BlocklistUi;
use crate::ui::radarr_ui::collections::CollectionsUi;
use crate::ui::radarr_ui::downloads::DownloadsUi;
use crate::ui::radarr_ui::history::HistoryUi;
use crate::ui::radarr_ui::indexers::IndexersUi;
use crate::ui::radarr_ui::library::LibraryUi;
use crate::ui::radarr_ui::root_folders::RootFoldersUi;
@@ -35,10 +36,12 @@ use crate::utils::convert_to_gb;
mod blocklist;
mod collections;
mod downloads;
mod history;
mod indexers;
mod library;
#[cfg(test)]
mod radarr_ui_tests;
mod radarr_ui_utils;
mod root_folders;
mod system;
@@ -61,6 +64,7 @@ impl DrawUi for RadarrUi {
_ if RootFoldersUi::accepts(route) => RootFoldersUi::draw(f, app, content_area),
_ if SystemUi::accepts(route) => SystemUi::draw(f, app, content_area),
_ if BlocklistUi::accepts(route) => BlocklistUi::draw(f, app, content_area),
_ if HistoryUi::accepts(route) => HistoryUi::draw(f, app, content_area),
_ => (),
}
}
+4 -3
View File
@@ -29,9 +29,10 @@ mod tests {
#[case(ActiveRadarrBlock::Collections, 1)]
#[case(ActiveRadarrBlock::Downloads, 2)]
#[case(ActiveRadarrBlock::Blocklist, 3)]
#[case(ActiveRadarrBlock::RootFolders, 4)]
#[case(ActiveRadarrBlock::Indexers, 5)]
#[case(ActiveRadarrBlock::System, 6)]
#[case(ActiveRadarrBlock::History, 4)]
#[case(ActiveRadarrBlock::RootFolders, 5)]
#[case(ActiveRadarrBlock::Indexers, 6)]
#[case(ActiveRadarrBlock::System, 7)]
fn test_radarr_ui_renders_radarr_tabs(
#[case] active_radarr_block: ActiveRadarrBlock,
#[case] index: usize,
+121
View File
@@ -0,0 +1,121 @@
use ratatui::text::Line;
use crate::models::radarr_models::{RadarrHistoryData, RadarrHistoryEventType, RadarrHistoryItem};
#[cfg(test)]
#[path = "radarr_ui_utils_tests.rs"]
mod radarr_ui_utils_tests;
pub(super) fn create_history_event_details(history_item: RadarrHistoryItem) -> Vec<Line<'static>> {
let RadarrHistoryItem {
source_title,
event_type,
data,
..
} = history_item;
let RadarrHistoryData {
indexer,
release_group,
nzb_info_url,
download_client,
download_client_name,
age,
published_date,
dropped_path,
imported_path,
message,
reason,
source_path,
path,
..
} = data;
let mut lines = vec![
Line::from(format!("Source Title: {}", source_title.text.trim_start())),
Line::from(format!("Event Type: {}", event_type)),
];
match event_type {
RadarrHistoryEventType::Grabbed => {
lines.push(Line::from(format!(
"Indexer: {}",
indexer.unwrap_or_default().trim_start(),
)));
lines.push(Line::from(format!(
"Release Group: {}",
release_group.unwrap_or_default().trim_start(),
)));
lines.push(Line::from(format!(
"NZB Info URL: {}",
nzb_info_url.unwrap_or_default().trim_start(),
)));
lines.push(Line::from(format!(
"Download Client: {}",
download_client
.or(download_client_name)
.unwrap_or_default()
.trim_start()
)));
lines.push(Line::from(format!(
"Age: {} days",
age.unwrap_or("0".to_owned()).trim_start(),
)));
lines.push(Line::from(format!(
"Published Date: {}",
published_date.unwrap_or_default().to_string().trim_start(),
)));
}
RadarrHistoryEventType::DownloadFolderImported => {
lines.push(Line::from(format!(
"Dropped Path: {}",
dropped_path.unwrap_or_default().trim_start(),
)));
lines.push(Line::from(format!(
"Imported Path: {}",
imported_path.unwrap_or_default().trim_start(),
)));
lines.push(Line::from(format!(
"Download Client: {}",
download_client
.or(download_client_name)
.unwrap_or_default()
.trim_start()
)));
}
RadarrHistoryEventType::DownloadFailed => {
lines.push(Line::from(format!(
"Download Client: {}",
download_client
.or(download_client_name)
.unwrap_or_default()
.trim_start(),
)));
lines.push(Line::from(format!(
"Message: {}",
message.unwrap_or_default().trim_start(),
)));
}
RadarrHistoryEventType::MovieFileDeleted => {
lines.push(Line::from(format!(
"Reason: {}",
reason.unwrap_or_default().trim_start(),
)));
}
RadarrHistoryEventType::MovieFileRenamed => {
lines.push(Line::from(format!(
"Source Path: {}",
source_path.unwrap_or_default().trim_start(),
)));
lines.push(Line::from(format!(
"Destination Path: {}",
path.unwrap_or_default().trim_start(),
)));
}
_ => {
lines.push(Line::from(String::new()));
lines.push(Line::from("No additional data available"));
}
}
lines
}
+236
View File
@@ -0,0 +1,236 @@
#[cfg(test)]
mod tests {
use chrono::Utc;
use ratatui::text::Line;
use crate::models::radarr_models::RadarrHistoryEventType;
use crate::models::radarr_models::{RadarrHistoryData, RadarrHistoryItem};
use crate::ui::radarr_ui::radarr_ui_utils::create_history_event_details;
use pretty_assertions::assert_eq;
#[test]
fn test_create_grabbed_history_event_details() {
let history_item = radarr_history_item(RadarrHistoryEventType::Grabbed);
let RadarrHistoryItem {
source_title,
data,
event_type,
..
} = history_item.clone();
let RadarrHistoryData {
indexer,
release_group,
nzb_info_url,
download_client,
download_client_name,
age,
published_date,
..
} = data;
let expected_vec = vec![
Line::from(format!("Source Title: {}", source_title.text.trim_start(),)),
Line::from(format!("Event Type: {event_type}")),
Line::from(format!(
"Indexer: {}",
indexer.unwrap_or_default().trim_start(),
)),
Line::from(format!(
"Release Group: {}",
release_group.unwrap_or_default().trim_start()
)),
Line::from(format!(
"NZB Info URL: {}",
nzb_info_url.unwrap_or_default().trim_start()
)),
Line::from(format!(
"Download Client: {}",
download_client
.or(download_client_name)
.unwrap_or_default()
.trim_start()
)),
Line::from(format!(
"Age: {} days",
age.unwrap_or("0".to_owned()).trim_start(),
)),
Line::from(format!(
"Published Date: {}",
published_date.unwrap_or_default().to_string().trim_start()
)),
];
let history_details_vec = create_history_event_details(history_item);
assert_eq!(expected_vec, history_details_vec);
}
#[test]
fn test_create_download_folder_imported_history_event_details() {
let history_item = radarr_history_item(RadarrHistoryEventType::DownloadFolderImported);
let RadarrHistoryItem {
source_title,
data,
event_type,
..
} = history_item.clone();
let RadarrHistoryData {
dropped_path,
imported_path,
download_client,
..
} = data;
let expected_vec = vec![
Line::from(format!("Source Title: {}", source_title.text.trim_start(),)),
Line::from(format!("Event Type: {event_type}")),
Line::from(format!(
"Dropped Path: {}",
dropped_path.unwrap_or_default().trim_start()
)),
Line::from(format!(
"Imported Path: {}",
imported_path.unwrap_or_default().trim_start()
)),
Line::from(format!(
"Download Client: {}",
download_client.unwrap_or_default().trim_start(),
)),
];
let history_details_vec = create_history_event_details(history_item);
assert_eq!(expected_vec, history_details_vec);
}
#[test]
fn test_create_download_failed_history_event_details() {
let history_item = radarr_history_item(RadarrHistoryEventType::DownloadFailed);
let RadarrHistoryItem {
source_title,
data,
event_type,
..
} = history_item.clone();
let RadarrHistoryData {
message,
download_client,
..
} = data;
let expected_vec = vec![
Line::from(format!("Source Title: {}", source_title.text.trim_start())),
Line::from(format!("Event Type: {event_type}")),
Line::from(format!(
"Download Client: {}",
download_client.unwrap_or_default().trim_start()
)),
Line::from(format!(
"Message: {}",
message.unwrap_or_default().trim_start()
)),
];
let history_details_vec = create_history_event_details(history_item);
assert_eq!(expected_vec, history_details_vec);
}
#[test]
fn test_create_movie_file_deleted_history_event_details() {
let history_item = radarr_history_item(RadarrHistoryEventType::MovieFileDeleted);
let RadarrHistoryItem {
source_title,
data,
event_type,
..
} = history_item.clone();
let RadarrHistoryData { reason, .. } = data;
let expected_vec = vec![
Line::from(format!("Source Title: {}", source_title.text.trim_start(),)),
Line::from(format!("Event Type: {event_type}")),
Line::from(format!(
"Reason: {}",
reason.unwrap_or_default().trim_start(),
)),
];
let history_details_vec = create_history_event_details(history_item);
assert_eq!(expected_vec, history_details_vec);
}
#[test]
fn test_create_movie_file_renamed_history_event_details() {
let history_item = radarr_history_item(RadarrHistoryEventType::MovieFileRenamed);
let RadarrHistoryItem {
source_title,
data,
event_type,
..
} = history_item.clone();
let RadarrHistoryData {
source_path, path, ..
} = data;
let expected_vec = vec![
Line::from(format!("Source Title: {}", source_title.text.trim_start(),)),
Line::from(format!("Event Type: {event_type}")),
Line::from(format!(
"Source Path: {}",
source_path.unwrap_or_default().trim_start(),
)),
Line::from(format!(
"Destination Path: {}",
path.unwrap_or_default().trim_start(),
)),
];
let history_details_vec = create_history_event_details(history_item);
assert_eq!(expected_vec, history_details_vec);
}
#[test]
fn test_create_no_data_history_event_details() {
let history_item = radarr_history_item(RadarrHistoryEventType::Unknown);
let RadarrHistoryItem {
source_title,
event_type,
..
} = history_item.clone();
let expected_vec = vec![
Line::from(format!("Source Title: {}", source_title.text.trim_start(),)),
Line::from(format!("Event Type: {event_type}")),
Line::from(String::new()),
Line::from("No additional data available"),
];
let history_details_vec = create_history_event_details(history_item);
assert_eq!(expected_vec, history_details_vec);
}
fn radarr_history_item(event_type: RadarrHistoryEventType) -> RadarrHistoryItem {
RadarrHistoryItem {
source_title: "test.source.title".into(),
event_type,
data: radarr_history_data(),
..RadarrHistoryItem::default()
}
}
fn radarr_history_data() -> RadarrHistoryData {
RadarrHistoryData {
dropped_path: Some("/dropped/test".into()),
imported_path: Some("/imported/test".into()),
indexer: Some("Test Indexer".into()),
release_group: Some("test release group".into()),
nzb_info_url: Some("test url".into()),
download_client: Some("test download client".into()),
download_client_name: None,
age: Some("1".into()),
published_date: Some(Utc::now()),
message: Some("test message".into()),
reason: Some("test reason".into()),
source_path: Some("/source/path".into()),
path: Some("/path".into()),
}
}
}
@@ -3,7 +3,7 @@ source: src/ui/radarr_ui/radarr_ui_tests.rs
expression: output
---
╭ Movies ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Collections │ Downloads │ Blocklist │ Root Folders │ Indexers │ System
│ Library │ Collections │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Movie Title ▼ Source Title Languages Quality Formats Date │
│=> Test z movie English HD - 1080p English 2024-02-10 07:28:45 UTC │
@@ -3,7 +3,7 @@ source: src/ui/radarr_ui/radarr_ui_tests.rs
expression: output
---
╭ Movies ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Collections │ Downloads │ Blocklist │ Root Folders │ Indexers │ System
│ Library │ Collections │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Collection ▼ Number of Movies Root Folder Path Quality Profile Search on Add Monitored │
│=> Test Collection 1 /nfs/movies HD - 1080p Yes 🏷 │
@@ -3,7 +3,7 @@ source: src/ui/radarr_ui/radarr_ui_tests.rs
expression: output
---
╭ Movies ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Collections │ Downloads │ Blocklist │ Root Folders │ Indexers │ System
│ Library │ Collections │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Title Percent Complete Size Output Path Indexer Download Client │
│=> Test Download Title 50% 3.30 GB /nfs/movies/Test kickass torrents transmission │
@@ -0,0 +1,54 @@
---
source: src/ui/radarr_ui/radarr_ui_tests.rs
expression: output
---
╭ Movies ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Collections │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Source Title ▼ Event Type Language Quality Date │
│=> Test grabbed English HD - 1080p 2022-12-30 07:37:56 UTC │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
│ │
╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
@@ -3,7 +3,7 @@ source: src/ui/radarr_ui/radarr_ui_tests.rs
expression: output
---
╭ Movies ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Collections │ Downloads │ Blocklist │ Root Folders │ Indexers │ System
│ Library │ Collections │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Indexer ▼ RSS Automatic Search Interactive Search Priority Tags │
│=> Test Indexer Enabled Enabled Enabled 25 alex │
@@ -3,7 +3,7 @@ source: src/ui/radarr_ui/radarr_ui_tests.rs
expression: output
---
╭ Movies ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Collections │ Downloads │ Blocklist │ Root Folders │ Indexers │ System
│ Library │ Collections │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Title ▼ Year Studio Runtime Rating Language Size Quality Profile Monitored Tags │
│=> Test 2023 21st Century Alex 2h 0m R English 3.30 GB HD - 1080p 🏷 alex │
@@ -3,7 +3,7 @@ source: src/ui/radarr_ui/radarr_ui_tests.rs
expression: output
---
╭ Movies ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Collections │ Downloads │ Blocklist │ Root Folders │ Indexers │ System
│ Library │ Collections │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Path Free Space Unmapped Folders │
│=> /nfs 204800.00 GB 0 │
@@ -3,7 +3,7 @@ source: src/ui/radarr_ui/radarr_ui_tests.rs
expression: output
---
╭ Movies ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Collections │ Downloads │ Blocklist │ Root Folders │ Indexers │ System
│ Library │ Collections │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│╭ Tasks ───────────────────────────────────────────────────────────────────────╮╭ Queued Events ──────────────────────────────────────────────────────────────╮│
││Name Interval Last Execution Last Duration Next Execution ││Trigger Status Name Queued Started Duration ││
││Backup 1 hour now 00:00:17 59 minutes ││manual completed Refresh Monitored 4 minutes ago 4 minutes a 00:03:03 ││
@@ -16,7 +16,7 @@ expression: output
│ ││ ││ │
╰───────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────╯╰──────────────────╯
╭ Movies ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Collections │ Downloads │ Blocklist │ Root Folders │ Indexers │ System
│ Library │ Collections │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Title ▼ Year Studio Runtime Rating Language Size Quality Profile Monitored Tags │
│=> Test 2023 21st Century Alex 2h 0m R English 3.30 GB HD - 1080p 🏷 alex │
@@ -19,7 +19,7 @@ expression: output
│ ││ ││ │
╰───────────────────────────────────────────────────────────────────────╯╰──────────────────────────────────────────────────────────────────────╯╰──────────────────╯
╭ Movies ─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ Library │ Collections │ Downloads │ Blocklist │ Root Folders │ Indexers │ System
│ Library │ Collections │ Downloads │ Blocklist │ History │ Root Folders │ Indexers │ System │
│───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────│
│ Title ▼ Year Studio Runtime Rating Language Size Quality Profile Monitored Tags │
│=> Test 2023 21st Century Alex 2h 0m R English 3.30 GB HD - 1080p 🏷 alex │