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) => { ) : (