diff --git a/admin/timescale/create_indexes.sql b/admin/timescale/create_indexes.sql index 093abaf89b..737d1147e8 100644 --- a/admin/timescale/create_indexes.sql +++ b/admin/timescale/create_indexes.sql @@ -81,7 +81,7 @@ CREATE UNIQUE INDEX similar_artist_credit_mbids_reverse_uniq_idx ON similarity.a CREATE INDEX similarity_overhyped_artists_artist_mbid_idx ON similarity.overhyped_artists(artist_mbid) INCLUDE (factor); -CREATE INDEX mbid_manual_mapping_top_idx ON mbid_manual_mapping_top (recording_msid) INCLUDE (recording_mbid); +CREATE INDEX mbid_manual_mapping_top_idx ON mbid_manual_mapping_top (recording_msid) INCLUDE (recording_mbid, release_mbid); CREATE INDEX popularity_recording_listen_count_idx ON popularity.recording (total_listen_count) INCLUDE (recording_mbid); CREATE INDEX popularity_recording_user_count_idx ON popularity.recording (total_user_count) INCLUDE (recording_mbid); diff --git a/admin/timescale/create_tables.sql b/admin/timescale/create_tables.sql index 035c5eed07..56d6799a75 100644 --- a/admin/timescale/create_tables.sql +++ b/admin/timescale/create_tables.sql @@ -62,6 +62,7 @@ CREATE TABLE mbid_manual_mapping( id INTEGER GENERATED ALWAYS AS IDENTITY NOT NULL, recording_msid UUID NOT NULL, recording_mbid UUID NOT NULL, + release_mbid UUID NOT NULL, user_id INTEGER NOT NULL, created TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL ); diff --git a/admin/timescale/create_views.sql b/admin/timescale/create_views.sql index cbc8c15ebd..de8e1a6516 100644 --- a/admin/timescale/create_views.sql +++ b/admin/timescale/create_views.sql @@ -8,9 +8,11 @@ CREATE MATERIALIZED VIEW mbid_manual_mapping_top AS ( SELECT DISTINCT ON (recording_msid) recording_msid , recording_mbid + , release_mbid FROM mbid_manual_mapping GROUP BY recording_msid , recording_mbid + , release_mbid HAVING count(DISTINCT user_id) >= 3 ORDER BY recording_msid , count(*) DESC diff --git a/admin/timescale/updates/2024-07-04-add-release-in-mbid-mapping.sql b/admin/timescale/updates/2024-07-04-add-release-in-mbid-mapping.sql new file mode 100644 index 0000000000..6ae0458991 --- /dev/null +++ b/admin/timescale/updates/2024-07-04-add-release-in-mbid-mapping.sql @@ -0,0 +1,29 @@ +BEGIN; + +ALTER TABLE mbid_manual_mapping ADD COLUMN release_mbid UUID NOT NULL; + + + +DROP MATERIALIZED VIEW mbid_manual_mapping_top; + +-- create a materialized view of the top mappings of a recording msid. top mappings are chosen +-- by the highest number of users that have added it. if multiple mappings have same count, break +-- ties by preferring the mapping that was created most recently. to avoid abuse, mappings are only +-- considered for this view if at least 3 separate users created those. +CREATE MATERIALIZED VIEW mbid_manual_mapping_top AS ( + SELECT DISTINCT ON (recording_msid) + recording_msid + , recording_mbid + , release_mbid + FROM mbid_manual_mapping + GROUP BY recording_msid + , recording_mbid + , release_mbid + HAVING count(DISTINCT user_id) >= 3 + ORDER BY recording_msid + , count(*) DESC + , max(created) DESC +); + + +COMMIT; \ No newline at end of file diff --git a/frontend/css/search-track.less b/frontend/css/search-track.less index 9473a45dad..64e50dfd6d 100644 --- a/frontend/css/search-track.less +++ b/frontend/css/search-track.less @@ -18,25 +18,26 @@ // Options within the dropdown menu > option, > button { .track-search-dropdown>a,.track-search-dropdown>button { - display: block; - width: 100%; - outline: inherit; - padding: 5px 20px; - font: inherit; - text-align: left; - background: none; - color: inherit; - border: none; - cursor: pointer; + display: block; + width: 100%; + outline: inherit; + padding: 5px 20px; + font: inherit; + text-align: left; + background: none; + color: inherit; + border: none; + cursor: pointer; - &:hover, - &:focus { - color: #fff; - background-color: #353070; + &:hover, + &:focus { + color: #fff; + background-color: #353070; + } } } } .track-search:focus-within .track-search-dropdown, .album-search:focus-within .track-search-dropdown { display: block; // show when parent container is focused -} +} \ No newline at end of file diff --git a/frontend/js/src/common/listens/MBIDMappingModal.tsx b/frontend/js/src/common/listens/MBIDMappingModal.tsx index 4346ee13c8..1bbc62b803 100644 --- a/frontend/js/src/common/listens/MBIDMappingModal.tsx +++ b/frontend/js/src/common/listens/MBIDMappingModal.tsx @@ -3,6 +3,7 @@ import NiceModal, { useModal } from "@ebay/nice-modal-react"; import { faTimesCircle } from "@fortawesome/free-regular-svg-icons"; import { + faChevronDown, faExchangeAlt, faInfoCircle, faQuestionCircle, @@ -23,29 +24,22 @@ import { getRecordingMSID, getTrackName, } from "../../utils/utils"; +import { getListenFromRecording } from "../../user/components/AddListenModal"; +import ReleaseCard from "../../explore/fresh-releases/components/ReleaseCard"; export type MBIDMappingModalProps = { listenToMap?: Listen; }; -function getListenFromSelectedRecording( - selectedRecordingMetadata?: TrackMetadata -): Listen | undefined { - if (!selectedRecordingMetadata) { - return undefined; - } - return { - listened_at: 0, - track_metadata: selectedRecordingMetadata, - }; -} - export default NiceModal.create(({ listenToMap }: MBIDMappingModalProps) => { const modal = useModal(); const { resolve, visible } = modal; const [copyTextClickCounter, setCopyTextClickCounter] = React.useState(0); const [selectedRecording, setSelectedRecording] = React.useState< - TrackMetadata + MusicBrainzRecordingWithReleasesAndRGs + >(); + const [selectedRelease, setSelectedRelease] = React.useState< + MusicBrainzRelease & WithReleaseGroup >(); const closeModal = React.useCallback(() => { @@ -92,7 +86,7 @@ export default NiceModal.create(({ listenToMap }: MBIDMappingModalProps) => { if (!listenToMap || !selectedRecording || !auth_token) { return; } - const selectedRecordingToListen = getListenFromSelectedRecording( + const selectedRecordingToListen = getListenFromRecording( selectedRecording ); const recordingMBID = @@ -104,7 +98,8 @@ export default NiceModal.create(({ listenToMap }: MBIDMappingModalProps) => { await APIService.submitMBIDMapping( auth_token, recordingMSID, - recordingMBID + recordingMBID, + selectedRelease?.id ); } catch (error) { handleError(error, "Error while linking listen"); @@ -133,6 +128,7 @@ export default NiceModal.create(({ listenToMap }: MBIDMappingModalProps) => { APIService, selectedRecording, handleError, + selectedRelease, ] ); @@ -143,9 +139,9 @@ export default NiceModal.create(({ listenToMap }: MBIDMappingModalProps) => { ); }, [listenToMap]); - const listenFromSelectedRecording = getListenFromSelectedRecording( - selectedRecording - ); + const listenFromSelectedRecording = selectedRecording + ? getListenFromRecording(selectedRecording) + : undefined; if (!listenToMap) { return null; @@ -251,7 +247,10 @@ export default NiceModal.create(({ listenToMap }: MBIDMappingModalProps) => { title="Reset" icon={faTimesCircle} iconSize="lg" - action={() => setSelectedRecording(undefined)} + action={() => { + setSelectedRecording(undefined); + setSelectedRelease(undefined); + }} /> } /> @@ -270,15 +269,115 @@ export default NiceModal.create(({ listenToMap }: MBIDMappingModalProps) => { ) : (
{ - setSelectedRecording(trackMetadata); + onSelectRecording={(newSelectedRecording) => { + setSelectedRecording(newSelectedRecording); + if (newSelectedRecording.releases?.length === 1) { + setSelectedRelease(newSelectedRecording.releases[0]); + } else { + setSelectedRelease(undefined); + } }} defaultValue={defaultValue} />
)} + {selectedRecording && selectedRecording?.releases?.length > 1 && ( + <> +
+ Choose from {selectedRecording?.releases?.length}{" "} + releases  + (optional)  + +
+
+
+ Too many choices? See more details{" "} + + on MusicBrainz + + . +
+
+ {selectedRecording.releases.map((release) => { + const releaseGroup = release["release-group"]; + return ( + + { + setSelectedRelease(release); + }} + releaseName={release.title} + releaseDate={release.date} + dateFormatOptions={{ + year: "numeric", + month: "short", + }} + releaseMBID={release.id} + releaseGroupMBID={releaseGroup?.id} + artistCreditName={selectedRecording[ + "artist-credit" + ] + ?.map( + (artist) => + `${artist.name}${artist.joinphrase}` + ) + .join("")} + artistCredits={selectedRecording[ + "artist-credit" + ].map((ac) => ({ + artist_credit_name: ac.name, + artist_mbid: ac.artist.id, + join_phrase: ac.joinphrase, + }))} + artistMBIDs={selectedRecording[ + "artist-credit" + ]?.map((ac) => ac.artist.id)} + releaseTypePrimary={ + releaseGroup?.["primary-type"] + } + releaseTypeSecondary={releaseGroup?.[ + "secondary-types" + ].join("+")} + caaID={null} + caaReleaseMBID={release.id} + showTags={false} + showArtist + showReleaseTitle + showInformation + /> + + ); + })} +
+
+ + )}