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
+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);