From caa6534ee8b3b0537940c228af91188caab85ea2 Mon Sep 17 00:00:00 2001 From: Antti Kettunen Date: Sun, 24 May 2026 21:29:22 +0300 Subject: [PATCH] feat(import): show MusicBrainz variants - pass release metadata through album search normalization - surface release format, country, label, and disambiguation in React import cards - add coverage for search normalization and import route rendering --- core/search/sources.py | 6 +++ tests/search/test_search_sources.py | 31 ++++++++++++++- webui/src/routes/import/-import.types.ts | 10 +++++ webui/src/routes/import/-route.test.tsx | 19 +++++++++ .../routes/import/-ui/album-import-tab.tsx | 39 +++++++++++++++---- .../routes/import/-ui/import-page.module.css | 12 ++++++ 6 files changed, 109 insertions(+), 8 deletions(-) diff --git a/core/search/sources.py b/core/search/sources.py index e66b9eda..006bf8f0 100644 --- a/core/search/sources.py +++ b/core/search/sources.py @@ -56,6 +56,12 @@ def search_kind(client, query: str, kind: str, source_name: Optional[str] = None "release_date": album.release_date, "total_tracks": album.total_tracks, "album_type": album.album_type, + "format": getattr(album, "format", None), + "country": getattr(album, "country", None), + "status": getattr(album, "status", None), + "label": getattr(album, "label", None), + "disambiguation": getattr(album, "disambiguation", None), + "release_group_id": getattr(album, "release_group_id", None), "external_urls": album.external_urls or {}, }) except Exception as e: diff --git a/tests/search/test_search_sources.py b/tests/search/test_search_sources.py index 48a93620..f6949027 100644 --- a/tests/search/test_search_sources.py +++ b/tests/search/test_search_sources.py @@ -19,7 +19,9 @@ class _Artist: class _Album: def __init__(self, id_, name, artists=None, image_url=None, release_date=None, - total_tracks=10, album_type='album', external_urls=None): + total_tracks=10, album_type='album', external_urls=None, format=None, + country=None, status=None, label=None, disambiguation=None, + release_group_id=None): self.id = id_ self.name = name self.artists = artists or [] @@ -28,6 +30,12 @@ class _Album: self.total_tracks = total_tracks self.album_type = album_type self.external_urls = external_urls + self.format = format + self.country = country + self.status = status + self.label = label + self.disambiguation = disambiguation + self.release_group_id = release_group_id class _Track: @@ -99,6 +107,27 @@ def test_search_kind_albums_handles_no_artists(): assert result[0]['artist'] == 'Unknown Artist' +def test_search_kind_albums_passthrough_release_metadata(): + client = _Client(albums=[_Album( + 'a1', + 'Variant', + artists=['Artist'], + format='CD', + country='US', + status='Official', + label='Fixture Records', + disambiguation='clean', + release_group_id='rg-1', + )]) + result = sources.search_kind(client, 'v', 'albums') + assert result[0]['format'] == 'CD' + assert result[0]['country'] == 'US' + assert result[0]['status'] == 'Official' + assert result[0]['label'] == 'Fixture Records' + assert result[0]['disambiguation'] == 'clean' + assert result[0]['release_group_id'] == 'rg-1' + + def test_search_kind_tracks_returns_full_shape(): client = _Client(tracks=[_Track('t1', 'Money', artists=['Pink Floyd'], album='DSOTM', duration_ms=383000, image_url='m.jpg', diff --git a/webui/src/routes/import/-import.types.ts b/webui/src/routes/import/-import.types.ts index 7192336c..a6298593 100644 --- a/webui/src/routes/import/-import.types.ts +++ b/webui/src/routes/import/-import.types.ts @@ -58,6 +58,11 @@ export interface ImportAlbumResult { image_url?: string | null; total_tracks?: number | null; release_date?: string | null; + format?: string | null; + country?: string | null; + disambiguation?: string | null; + status?: string | null; + label?: string | null; } export interface ImportAlbumSearchPayload { @@ -98,6 +103,11 @@ export interface ImportAlbum { image_url?: string | null; total_tracks?: number | null; release_date?: string | null; + format?: string | null; + country?: string | null; + disambiguation?: string | null; + status?: string | null; + label?: string | null; } export interface ImportAlbumTrack { diff --git a/webui/src/routes/import/-route.test.tsx b/webui/src/routes/import/-route.test.tsx index 679341c5..b8572b95 100644 --- a/webui/src/routes/import/-route.test.tsx +++ b/webui/src/routes/import/-route.test.tsx @@ -97,6 +97,11 @@ describe('import route', () => { source: 'deezer', total_tracks: 1, release_date: '2026-01-01', + format: 'CD', + country: 'US', + disambiguation: '25th Anniversary Edition', + status: 'official', + label: 'MusicBrainz', }, ], }); @@ -113,6 +118,11 @@ describe('import route', () => { source: 'deezer', total_tracks: 1, release_date: '2026-01-01', + format: 'CD', + country: 'US', + disambiguation: '25th Anniversary Edition', + status: 'official', + label: 'MusicBrainz', }, ], }); @@ -130,6 +140,11 @@ describe('import route', () => { source: 'deezer', total_tracks: 1, release_date: '2026-01-01', + format: 'CD', + country: 'US', + disambiguation: '25th Anniversary Edition', + status: 'official', + label: 'MusicBrainz', }, matches: [ { @@ -196,6 +211,10 @@ describe('import route', () => { await waitFor(() => expect(screen.getByTestId('import-page')).toBeInTheDocument()); expect(await screen.findByText('Import Music')).toBeInTheDocument(); expect(screen.getByText('Import: /music/Staging')).toBeInTheDocument(); + expect( + await screen.findByText('1 tracks · 2026 · CD · US · 25th Anniversary Edition'), + ).toBeInTheDocument(); + expect(screen.getByText('official · MusicBrainz')).toBeInTheDocument(); expect( await screen.findByText('Showing Deezer results - not from your primary source (Spotify).'), ).toBeInTheDocument(); diff --git a/webui/src/routes/import/-ui/album-import-tab.tsx b/webui/src/routes/import/-ui/album-import-tab.tsx index 28602a3c..631907e6 100644 --- a/webui/src/routes/import/-ui/album-import-tab.tsx +++ b/webui/src/routes/import/-ui/album-import-tab.tsx @@ -216,6 +216,16 @@ function useAlbumImportViewModel() { type AlbumImportViewModel = ReturnType; +type AlbumMetaFields = { + total_tracks?: number | null; + release_date?: string | null; + format?: string | null; + country?: string | null; + disambiguation?: string | null; + status?: string | null; + label?: string | null; +}; + export function AlbumImportTab() { const viewModel = useAlbumImportViewModel(); @@ -394,6 +404,8 @@ function AlbumCard({ onSelect: (album: ImportAlbumResult) => void; }) { const resultSourceBadge = getImportSourceBadgeText(album.source, lookupSource); + const metaParts = getAlbumMetaParts(album); + const detailParts = getAlbumDetailParts(album); return (