From 1c21bc9efc94957a7155dcf73d5a358f7160e6bb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=A1s=20Tamargo?= Date: Tue, 7 Jan 2025 17:35:54 +0100 Subject: [PATCH] MBS-13870: Add filter for recordings not related to works This allows filtering an artist's recording list by whether the recordings have at least one work related to them, or they have none. This is admittedly mostly useful for editing, but it can be *very* useful for that, allowing users to easily find recordings that still need work relationships - it would make it much easier to improve the discographies of prolific classical performers, such as orchestras and conductors, and probably be of use also for popular music. --- lib/MusicBrainz/Server/Data/Recording.pm | 16 ++++++++ .../Server/Form/Filter/Recording.pm | 14 ++++++- .../scripts/common/components/FilterForm.js | 18 +++++++++ .../Server/Controller/Artist/Filtering.pm | 39 ++++++++++++++++--- t/sql/filtering.sql | 3 +- 5 files changed, 83 insertions(+), 7 deletions(-) diff --git a/lib/MusicBrainz/Server/Data/Recording.pm b/lib/MusicBrainz/Server/Data/Recording.pm index d00a541da82..e780de0c670 100644 --- a/lib/MusicBrainz/Server/Data/Recording.pm +++ b/lib/MusicBrainz/Server/Data/Recording.pm @@ -126,6 +126,22 @@ sub find_by_artist push @where_query, 'recording.artist_credit = ?'; push @where_args, $filter{artist_credit_id}; } + if (exists $filter{works}) { + my $works_query = <<~'SQL'; + EXISTS ( + SELECT TRUE + FROM l_recording_work lrw + WHERE lrw.entity0 = recording.id + ) + SQL + if ($filter{works} == 1) { + # Show only recordings with works + push @where_query, $works_query; + } elsif ($filter{works} == 2) { + # Show only recordings without works + push @where_query, "NOT $works_query"; + } + } if (exists $filter{hide_bootlegs}) { push @where_query, <<~'SQL'; ( diff --git a/lib/MusicBrainz/Server/Form/Filter/Recording.pm b/lib/MusicBrainz/Server/Form/Filter/Recording.pm index 3656ea8668f..ceb765af710 100644 --- a/lib/MusicBrainz/Server/Form/Filter/Recording.pm +++ b/lib/MusicBrainz/Server/Form/Filter/Recording.pm @@ -21,12 +21,16 @@ has_field 'video' => ( type => 'Select', ); +has_field 'works' => ( + type => 'Select', +); + has_field 'hide_bootlegs' => ( type => 'Checkbox', ); sub filter_field_names { - return qw/ disambiguation name artist_credit_id hide_bootlegs video /; + return qw/ disambiguation name artist_credit_id hide_bootlegs video works /; } sub options_artist_credit_id { @@ -44,12 +48,20 @@ sub options_video { ]; } +sub options_works { + return [ + { value => 1, label => l('Related to works') }, + { value => 2, label => l('Not related to works') }, + ]; +} + around TO_JSON => sub { my ($orig, $self) = @_; my $json = $self->$orig; $json->{options_artist_credit_id} = $self->options_artist_credit_id; $json->{options_video} = $self->options_video; + $json->{options_works} = $self->options_works; return $json; }; diff --git a/root/static/scripts/common/components/FilterForm.js b/root/static/scripts/common/components/FilterForm.js index b3d4aaa4fde..16071815b16 100644 --- a/root/static/scripts/common/components/FilterForm.js +++ b/root/static/scripts/common/components/FilterForm.js @@ -33,6 +33,7 @@ type RecordingFilterFormT = FormT<{ +artist_credit_id: FieldT, +hide_bootlegs: FieldT, +video: FieldT, + +works: FieldT, }>; export type RecordingFilterT = $ReadOnly<{ @@ -40,6 +41,7 @@ export type RecordingFilterT = $ReadOnly<{ +entity_type: 'recording', +options_artist_credit_id: SelectOptionsT, +options_video: SelectOptionsT, + +options_works: SelectOptionsT, }>; type ReleaseFilterFormT = FormT<{ @@ -224,6 +226,22 @@ component FilterForm(form: FilterFormT) { /> + + + {addColonText(l('Works'))} + + + + + sub { my $tx = test_xpath_html($mech->content); $tx->is( 'count(//table[@class="tbl"]/tbody/tr)', - '4', + '5', 'There are four entries in the unfiltered recording table', ); @@ -527,8 +527,37 @@ test 'Recording page filtering' => sub { $tx = test_xpath_html($mech->content); $tx->is( 'count(//table[@class="tbl"]/tbody/tr)', - '3', - 'There are three entries in the recording table after filtering by non-videos only', + '4', + 'There are four entries in the recording table after filtering by non-videos only', + ); + + $mech->get_ok( + '/artist/af4c43d3-c0e0-421e-ac64-000329af0435/recordings?filter.works=1', + 'Fetched artist recordings page with related works option', + ); + + $tx = test_xpath_html($mech->content); + $tx->is( + 'count(//table[@class="tbl"]/tbody/tr)', + '4', + 'There are four entries in the recording table after filtering to show recordings with works only', + ); + + $mech->get_ok( + '/artist/af4c43d3-c0e0-421e-ac64-000329af0435/recordings?filter.works=2', + 'Fetched artist recordings page with no related works option', + ); + + $tx = test_xpath_html($mech->content); + $tx->is( + 'count(//table[@class="tbl"]/tbody/tr)', + '1', + 'There is one entry in the recording table after filtering to show recordings without works only', + ); + $tx->is( + '//table[@class="tbl"]/tbody/tr/td[1]', + 'Improvisation (No work here)', + 'The entry is named "Improvisation"', ); $mech->get_ok( @@ -551,8 +580,8 @@ test 'Recording page filtering' => sub { $tx = test_xpath_html($mech->content); $tx->is( 'count(//table[@class="tbl"]/tbody/tr)', - '3', - 'There are three entries in the recording table after filtering by non-bootleg only', + '4', + 'There are four entries in the recording table after filtering by non-bootleg only', ); }; diff --git a/t/sql/filtering.sql b/t/sql/filtering.sql index bdc8d82f89f..4b285051b3e 100644 --- a/t/sql/filtering.sql +++ b/t/sql/filtering.sql @@ -93,7 +93,8 @@ INSERT INTO recording (id, gid, name, artist_credit, video, comment) VALUES (3400, 'ce82bfa1-733a-494a-aaa0-fc5de79bd54f', 'Interludium', 3402, 't', 'Testy'), (3401, 'd9c7a74e-3c08-48b1-be2f-5d9a144f2c08', 'Symphony no. 3', 3401, 'f', 'Testy 2'), (3402, 'd9c7a74e-3c08-48b1-be2f-5d9a144f2c01', 'Brandenburg Concerto no. 5', 3401, 'f', ''), - (3403, 'd9c7a74e-3c08-48b1-be2f-5d9a144f2c02', 'Brandenburg Concerto no. 5', 3402, 'f', ''); + (3403, 'd9c7a74e-3c08-48b1-be2f-5d9a144f2c02', 'Brandenburg Concerto no. 5', 3402, 'f', ''), + (3405, 'd9c7a74e-3c08-48b1-be2f-5d9a144f2c09', 'Improvisation', 3401, 'f', 'No work here'); INSERT INTO track (id, gid, recording, medium, position, number, name, artist_credit) VALUES (3400, 'ce82bfa1-aaaa-494a-aaa0-fc5de79bd54f', 3400, 3400, 1, 1, 'Interludium', 3402), -- to make recording not bootleg-only