From 11ed8343492bb2019e64af3b444aa2a5c003d06c Mon Sep 17 00:00:00 2001 From: granth23 Date: Tue, 11 Feb 2025 00:54:13 +0530 Subject: [PATCH 01/21] new stats added for user --- frontend/js/src/user/stats/UserReports.tsx | 7 + .../stats/components/UserArtistActivity.tsx | 153 +++++ frontend/js/src/utils/APIService.ts | 18 + frontend/js/src/utils/types.d.ts | 11 + listenbrainz/webserver/views/stats_api.py | 540 +++++++++++++++++- 5 files changed, 725 insertions(+), 4 deletions(-) create mode 100644 frontend/js/src/user/stats/components/UserArtistActivity.tsx diff --git a/frontend/js/src/user/stats/UserReports.tsx b/frontend/js/src/user/stats/UserReports.tsx index 3016587cb7..2bc40fbac5 100644 --- a/frontend/js/src/user/stats/UserReports.tsx +++ b/frontend/js/src/user/stats/UserReports.tsx @@ -16,6 +16,7 @@ import UserListeningActivity from "./components/UserListeningActivity"; import UserTopEntity from "./components/UserTopEntity"; import UserDailyActivity from "./components/UserDailyActivity"; import UserArtistMap from "./components/UserArtistMap"; +import UserArtistActivity from "./components/UserArtistActivity"; import { getAllStatRanges, isInvalidStatRange } from "./utils"; import GlobalAppContext from "../../utils/GlobalAppContext"; import StatsExplanationsModal from "../../common/stats/StatsExplanationsModal"; @@ -167,6 +168,12 @@ export default function UserReports() { )} + {user && ( +
+ {statsExplanationModalButton} + +
+ )}
{statsExplanationModalButton} diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx new file mode 100644 index 0000000000..997377fe0d --- /dev/null +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -0,0 +1,153 @@ +import { ResponsiveBar } from "@nivo/bar"; +import * as React from "react"; +import { faExclamationCircle, faLink } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { IconProp } from "@fortawesome/fontawesome-svg-core"; +import { useQuery } from "@tanstack/react-query"; +import Card from "../../../components/Card"; +import Loader from "../../../components/Loader"; +import { COLOR_BLACK } from "../../../utils/constants"; +import GlobalAppContext from "../../../utils/GlobalAppContext"; + +export type UserArtistActivityProps = { + range: UserStatsAPIRange; + user: ListenBrainzUser; +}; + +export type UserArtistActivityState = { + data: UserArtistActivityResponse; + loading: boolean; + errorMessage: string; + hasError: boolean; +}; + +export default function UserArtistActivity({ + range, + user, +}: UserArtistActivityProps) { + const { APIService } = React.useContext(GlobalAppContext); + + const { data: loaderData, isLoading: loading } = useQuery({ + queryKey: ["userArtistActivity", user.name, range], + queryFn: async () => { + try { + const queryData = await APIService.getUserArtistActivity( + user.name, + range + ); + return { data: queryData, hasError: false, errorMessage: "" }; + } catch (error) { + return { + data: { result: [] } as UserArtistActivityResponse, + hasError: true, + errorMessage: error.message, + }; + } + }, + }); + + const { + data: rawData = { result: [] } as UserArtistActivityResponse, + hasError = false, + errorMessage = "", + } = loaderData || {}; + + const processData = (data?: UserArtistActivityResponse) => { + if (!data || !data.result || data.result.length === 0) { + return []; + } + return data.result.map((artist) => ({ + label: artist.name.split(",")[0], + ...artist.albums.reduce( + (acc, album) => ({ ...acc, [album.name]: album.listen_count }), + {} as Record + ), + })) as ChartDataItem[]; + }; + + type ChartDataItem = { + label: string; + } & Record; + + const [chartData, setChartData] = React.useState([]); + + React.useEffect(() => { + if (rawData && rawData.result.length > 0) { + console.log("rawData", rawData); + const processedData = processData(rawData); + console.log("processedData", processedData); + setChartData(processedData); + } + }, [rawData]); + + React.useEffect(() => { + console.log("Updated chartData", chartData); + }, [chartData]); + + return ( + +
+
+

+ Artist Activity +

+
+
+

+ + + +

+
+
+ + {hasError ? ( +
+ + {" "} + {errorMessage} + +
+ ) : ( +
+
+
+ + Object.keys(item).filter((key) => key !== "label") + ) + ) + )} + indexBy="label" + margin={{ top: 20, right: 80, bottom: 60, left: 80 }} + padding={0.2} + layout="vertical" + colors={{ scheme: "pink_yellowGreen" }} + borderColor={{ from: "color", modifiers: [["darker", 1.6]] }} + enableLabel={false} + /> +
+
+
+ )} +
+
+ ); +} diff --git a/frontend/js/src/utils/APIService.ts b/frontend/js/src/utils/APIService.ts index 93d1f2a674..310936cd54 100644 --- a/frontend/js/src/utils/APIService.ts +++ b/frontend/js/src/utils/APIService.ts @@ -574,6 +574,24 @@ export default class APIService { return response.json(); }; + getUserArtistActivity = async ( + userName: string, + range: UserStatsAPIRange = "all_time" + ): Promise => { + const url = `${this.APIBaseURI}/stats/user/${userName}/artist-activity?range=${range}`; + const response = await fetch(url); + await this.checkStatus(response); + if (response.status === 204) { + const error = new APIError( + "There are no statistics available for this user for this period" + ); + error.status = response.statusText; + error.response = response; + throw error; + } + return response.json(); + }; + getUserArtistMap = async ( userName?: string, range: UserStatsAPIRange = "all_time", diff --git a/frontend/js/src/utils/types.d.ts b/frontend/js/src/utils/types.d.ts index a3631060f1..5954ca759e 100644 --- a/frontend/js/src/utils/types.d.ts +++ b/frontend/js/src/utils/types.d.ts @@ -288,6 +288,17 @@ declare type UserDailyActivityResponse = { }; }; +declare type UserArtistActivityResponse = { + result: Array<{ + name: string; + listen_count: number; + albums: Array<{ + name: string; + listen_count: number; + }>; + }>; +}; + declare type UserArtistMapArtist = { artist_name: string; artist_mbid: string; diff --git a/listenbrainz/webserver/views/stats_api.py b/listenbrainz/webserver/views/stats_api.py index a39a4c755f..9eb86d63e5 100644 --- a/listenbrainz/webserver/views/stats_api.py +++ b/listenbrainz/webserver/views/stats_api.py @@ -301,7 +301,7 @@ def get_recording(user_name): return _get_entity_stats(user_name, "recordings", "total_recording_count") -def _get_entity_stats(user_name: str, entity: str, count_key: str): +def _get_entity_stats(user_name: str, entity: str, count_key: str, entire_range: bool = False): user, stats_range = _validate_stats_user_params(user_name) offset = get_non_negative_param("offset", default=0) @@ -311,7 +311,7 @@ def _get_entity_stats(user_name: str, entity: str, count_key: str): if stats is None: raise APINoContent('') - entity_list, total_entity_count = _process_user_entity(stats, offset, count) + entity_list, total_entity_count = _process_user_entity(stats, offset, count, entire_range) return jsonify({"payload": { "user_id": user_name, entity: entity_list, @@ -409,6 +409,535 @@ def get_listening_activity(user_name: str): }}) +@stats_api_bp.get("/user//artist-activity") +@crossdomain +@ratelimit() +def get_artist_activity(user_name: str): + """ + Get the artist activity for user ``user_name``. The artist activity shows the total number of listens + for each artist along with their albums and corresponding listen counts. + + A sample response from the endpoint may look like: + + .. code-block:: json + + { + "result": [ + { + "name": "Radiohead", + "listen_count": 120, + "albums": [ + {"name": "OK Computer", "listen_count": 45}, + {"name": "In Rainbows", "listen_count": 75} + ] + }, + { + "name": "The Beatles", + "listen_count": 95, + "albums": [ + {"name": "Abbey Road", "listen_count": 60}, + {"name": "Revolver", "listen_count": 35} + ] + } + ] + } + + .. note:: + + - The example above shows artist activity data with two artists and their respective albums. + - The statistics are aggregated based on the number of listens recorded for each artist and their albums. + + :statuscode 200: Successful query, you have data! + :statuscode 204: Statistics for the user haven't been calculated, empty response will be returned + :statuscode 400: Bad request, check ``response['error']`` for more details + :statuscode 404: User not found + :resheader Content-Type: *application/json* + """ + # data = _get_entity_stats(user_name, "release_groups", "total_release_group_count") + data = { + "payload": { + "count": 25, + "from_ts": 1738540800, + "last_updated": 1739157484, + "offset": 0, + "range": "week", + "release_groups": [ + { + "artist_mbids": [ + "addca31a-40ae-4152-afb8-a477da5191f0" + ], + "artist_name": "Anuv Jain", + "artists": [ + { + "artist_credit_name": "Anuv Jain", + "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", + "join_phrase": "" + } + ], + "caa_id": 33591541837, + "caa_release_mbid": "67b5a2df-0b21-4b4e-b1ac-1b10cf8e164a", + "listen_count": 12, + "release_group_mbid": "1def5e40-cad4-43de-a774-be7eddf6aa6b", + "release_group_name": "Ocean" + }, + { + "artist_mbids": [ + "93e6118e-7fa8-49f6-9e02-699a1ebce105" + ], + "artist_name": "The Local Train", + "artists": [ + { + "artist_credit_name": "The Local Train", + "artist_mbid": "93e6118e-7fa8-49f6-9e02-699a1ebce105", + "join_phrase": "" + } + ], + "caa_id": 26294866570, + "caa_release_mbid": "072f7802-e9c7-487b-b3b2-07d91ac89b11", + "listen_count": 10, + "release_group_mbid": "4f0ccffd-feeb-4d50-a724-31f0eabbc750", + "release_group_name": "Aalas Ka Pedh" + }, + { + "artist_mbids": [ + "addca31a-40ae-4152-afb8-a477da5191f0" + ], + "artist_name": "Anuv Jain", + "artists": [ + { + "artist_credit_name": "Anuv Jain", + "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", + "join_phrase": "" + } + ], + "caa_id": 33600724905, + "caa_release_mbid": "91099f15-6691-487d-a889-7933e014766b", + "listen_count": 7, + "release_group_mbid": "ecd8bfb6-8f4b-4a79-a7fc-b585474e6a49", + "release_group_name": "Baarishein" + }, + { + "artist_mbids": [ + "addca31a-40ae-4152-afb8-a477da5191f0" + ], + "artist_name": "Anuv Jain", + "artists": [ + { + "artist_credit_name": "Anuv Jain", + "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", + "join_phrase": "" + } + ], + "caa_id": 40637299178, + "caa_release_mbid": "462e52cf-01e1-40c8-8ad5-59dfc519afcf", + "listen_count": 5, + "release_group_mbid": "0fa837e4-ec5c-4cad-99a1-d3f84258f5ab", + "release_group_name": "Jo Tum Mere Ho" + }, + { + "artist_mbids": [ + "addca31a-40ae-4152-afb8-a477da5191f0" + ], + "artist_name": "Anuv Jain", + "artists": [ + { + "artist_credit_name": "Anuv Jain", + "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", + "join_phrase": "" + } + ], + "caa_id": 33599951290, + "caa_release_mbid": "b457bb52-dafd-4042-ab2e-91f252fdc822", + "listen_count": 5, + "release_group_mbid": "39bf6cbc-5d91-4ec6-9aca-961cdff0f8bb", + "release_group_name": "Gul" + }, + { + "artist_mbids": [ + "f931c961-b647-4861-be8c-f47d84a4de51" + ], + "artist_name": "Diljit Dosanjh", + "artists": [ + { + "artist_credit_name": "Diljit Dosanjh", + "artist_mbid": "f931c961-b647-4861-be8c-f47d84a4de51", + "join_phrase": "" + } + ], + "caa_id": 33030341692, + "caa_release_mbid": "f9fc0be7-8b5f-4382-bc2a-182938c8a765", + "listen_count": 5, + "release_group_mbid": "23279dea-32b0-47a5-bc4a-68924262c5ec", + "release_group_name": "Drive Thru" + }, + { + "artist_mbids": [ + "ed29f721-0d40-4a90-9f25-a91c2ccccd5e" + ], + "artist_name": "AP Dhillon", + "artists": [ + { + "artist_credit_name": "AP Dhillon", + "artist_mbid": "ed29f721-0d40-4a90-9f25-a91c2ccccd5e", + "join_phrase": "" + } + ], + "caa_id": 33759408581, + "caa_release_mbid": "87a65c84-4d04-4b61-9687-3df76ba83bb2", + "listen_count": 4, + "release_group_mbid": "a659496c-1093-48af-88d5-773f96179e39", + "release_group_name": "Two Hearts Never Break The Same" + }, + { + "artist_mbids": [], + "artist_name": "Jasleen Royal, Stebin Ben, Vijay Deverakonda, Radhikka Madan, Priya Saraiya, Aditya Sharma", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 4, + "release_group_mbid": None, + "release_group_name": "Sahiba" + }, + { + "artist_mbids": [ + "addca31a-40ae-4152-afb8-a477da5191f0" + ], + "artist_name": "Anuv Jain", + "artists": [ + { + "artist_credit_name": "Anuv Jain", + "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", + "join_phrase": "" + } + ], + "caa_id": 33591219607, + "caa_release_mbid": "1e3e4f14-4139-44b5-a08c-c5beee6e8409", + "listen_count": 4, + "release_group_mbid": "6d04c3a2-0080-4aae-8069-95f0738958fb", + "release_group_name": "Meri Baaton Mein Tu" + }, + { + "artist_mbids": [ + "addca31a-40ae-4152-afb8-a477da5191f0" + ], + "artist_name": "Anuv Jain", + "artists": [ + { + "artist_credit_name": "Anuv Jain", + "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", + "join_phrase": "" + } + ], + "caa_id": 33583528095, + "caa_release_mbid": "429f1274-baba-41b4-a927-4fc6b2b49c35", + "listen_count": 4, + "release_group_mbid": "90aaddd2-65d5-41e8-9259-ece05d0b93db", + "release_group_name": "Mazaak" + }, + { + "artist_mbids": [ + "4a779683-5404-4b90-a0d7-242495158265" + ], + "artist_name": "Karan Aujla", + "artists": [ + { + "artist_credit_name": "Karan Aujla", + "artist_mbid": "4a779683-5404-4b90-a0d7-242495158265", + "join_phrase": "" + } + ], + "caa_id": 36478339379, + "caa_release_mbid": "f0537077-e571-4fa2-a907-8f9168329423", + "listen_count": 4, + "release_group_mbid": "5995145d-f279-48f2-a397-097e101a718a", + "release_group_name": "Making Memories" + }, + { + "artist_mbids": [ + "addca31a-40ae-4152-afb8-a477da5191f0" + ], + "artist_name": "Anuv Jain", + "artists": [ + { + "artist_credit_name": "Anuv Jain", + "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", + "join_phrase": "" + } + ], + "caa_id": 33590899663, + "caa_release_mbid": "04def11c-5b23-4bee-b822-1bcd4786aa4e", + "listen_count": 3, + "release_group_mbid": "992b3483-52e5-4cb3-be44-cf8113da5a62", + "release_group_name": "Riha" + }, + { + "artist_mbids": [ + "addca31a-40ae-4152-afb8-a477da5191f0" + ], + "artist_name": "Anuv Jain", + "artists": [ + { + "artist_credit_name": "Anuv Jain", + "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", + "join_phrase": "" + } + ], + "caa_id": 33599465996, + "caa_release_mbid": "aa89caf2-613d-4f3c-9c5f-4514142d867f", + "listen_count": 3, + "release_group_mbid": "4d45defd-3293-4444-9943-def2711fe093", + "release_group_name": "Mishri" + }, + { + "artist_mbids": [ + "f931c961-b647-4861-be8c-f47d84a4de51" + ], + "artist_name": "Diljit Dosanjh", + "artists": [ + { + "artist_credit_name": "Diljit Dosanjh", + "artist_mbid": "f931c961-b647-4861-be8c-f47d84a4de51", + "join_phrase": "" + } + ], + "caa_id": 37129944334, + "caa_release_mbid": "962f6b5a-d6ca-4c02-b47c-b3752b1f90c5", + "listen_count": 3, + "release_group_mbid": "3920b520-1404-495b-8557-5b2091233a14", + "release_group_name": "Ghost" + }, + { + "artist_mbids": [ + "addca31a-40ae-4152-afb8-a477da5191f0" + ], + "artist_name": "Anuv Jain", + "artists": [ + { + "artist_credit_name": "Anuv Jain", + "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", + "join_phrase": "" + } + ], + "caa_id": 33599116598, + "caa_release_mbid": "ad42e780-12cb-4418-a33a-ad4c2f3bc65e", + "listen_count": 3, + "release_group_mbid": "80b85e66-7fe8-4d93-9a0e-8595e465a713", + "release_group_name": "Alag Aasmaan" + }, + { + "artist_mbids": [ + "93e6118e-7fa8-49f6-9e02-699a1ebce105" + ], + "artist_name": "The Local Train", + "artists": [ + { + "artist_credit_name": "The Local Train", + "artist_mbid": "93e6118e-7fa8-49f6-9e02-699a1ebce105", + "join_phrase": "" + } + ], + "caa_id": 26294968495, + "caa_release_mbid": "852b618b-5c35-4491-9dcd-90794b54d876", + "listen_count": 2, + "release_group_mbid": "f408529f-0e6b-4933-a260-70715b5862f7", + "release_group_name": "Vaaqif" + }, + { + "artist_mbids": [ + "fe51ec18-bab4-4774-b0f5-ff0f175a41a7", + "41416396-17a2-478c-bf1e-209494d5f160" + ], + "artist_name": "Juss & MixSingh", + "artists": [ + { + "artist_credit_name": "Juss", + "artist_mbid": "fe51ec18-bab4-4774-b0f5-ff0f175a41a7", + "join_phrase": " & " + }, + { + "artist_credit_name": "MixSingh", + "artist_mbid": "41416396-17a2-478c-bf1e-209494d5f160", + "join_phrase": "" + } + ], + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 2, + "release_group_mbid": "2415d5c9-650b-4858-82d0-087dd60101fc", + "release_group_name": "Suniyan Suniyan" + }, + { + "artist_mbids": [ + "ed29f721-0d40-4a90-9f25-a91c2ccccd5e" + ], + "artist_name": "AP Dhillon", + "artists": [ + { + "artist_credit_name": "AP Dhillon", + "artist_mbid": "ed29f721-0d40-4a90-9f25-a91c2ccccd5e", + "join_phrase": "" + } + ], + "caa_id": 33215776810, + "caa_release_mbid": "938fd497-d709-4e53-b3bf-545b18d9faf0", + "listen_count": 2, + "release_group_mbid": "284ec2d6-af21-47e9-92e1-4dd44befa85d", + "release_group_name": "Summer High" + }, + { + "artist_mbids": [ + "5d77d4cf-febf-4e5c-b8e7-16ea5ffb0439" + ], + "artist_name": "Aditya Rikhari", + "artists": [ + { + "artist_credit_name": "Aditya Rikhari", + "artist_mbid": "5d77d4cf-febf-4e5c-b8e7-16ea5ffb0439", + "join_phrase": "" + } + ], + "caa_id": 35225071094, + "caa_release_mbid": "dd67ee08-49ef-4508-bf86-8c3eaf69791b", + "listen_count": 2, + "release_group_mbid": "447d85e7-2b6a-4974-9ecb-21f18678dc8c", + "release_group_name": "Samjho Na" + }, + { + "artist_mbids": [ + "1b8f90e7-435b-4a6a-a62d-ea154a8191fe" + ], + "artist_name": "Faheem Abdullah", + "artists": [ + { + "artist_credit_name": "Faheem Abdullah", + "artist_mbid": "1b8f90e7-435b-4a6a-a62d-ea154a8191fe", + "join_phrase": "" + } + ], + "caa_id": 40800551557, + "caa_release_mbid": "9a938666-b5a3-4612-8050-9540a1a5f479", + "listen_count": 2, + "release_group_mbid": "2892401d-a4f8-4791-9329-1a7b5834fe52", + "release_group_name": "Lost;Found" + }, + { + "artist_mbids": [ + "810b210f-b461-4f02-aff2-dff91196147e" + ], + "artist_name": "Prateek Kuhad", + "artists": [ + { + "artist_credit_name": "Prateek Kuhad", + "artist_mbid": "810b210f-b461-4f02-aff2-dff91196147e", + "join_phrase": "" + } + ], + "caa_id": 34528409438, + "caa_release_mbid": "6f1bbe0d-1663-4dd5-947a-8e780610e4c5", + "listen_count": 2, + "release_group_mbid": "f68f3c53-3f64-412a-9b3e-636154d077b0", + "release_group_name": "Kasoor" + }, + { + "artist_mbids": [], + "artist_name": "Aditya Rikhari, Tulsi Kumar, Lijo George-Dj Chetas", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 2, + "release_group_mbid": None, + "release_group_name": "Jaana Samjho Na (From \"Bhool Bhulaiyaa 3\")" + }, + { + "artist_mbids": [ + "f931c961-b647-4861-be8c-f47d84a4de51" + ], + "artist_name": "Diljit Dosanjh", + "artists": [ + { + "artist_credit_name": "Diljit Dosanjh", + "artist_mbid": "f931c961-b647-4861-be8c-f47d84a4de51", + "join_phrase": "" + } + ], + "caa_id": 28031589733, + "caa_release_mbid": "2906b920-a0df-49b3-9669-19f8a1847d20", + "listen_count": 2, + "release_group_mbid": "2fa55494-4806-4425-9e5e-d70e07af4f07", + "release_group_name": "G.O.A.T." + }, + { + "artist_mbids": [ + "ed29f721-0d40-4a90-9f25-a91c2ccccd5e", + "56743d2c-b315-4d01-a444-fa42088182c0", + "f903e686-a5f4-40e1-8fa8-c8132369c51e" + ], + "artist_name": "AP Dhillon, Gurinder Gill, Intense", + "artists": [ + { + "artist_credit_name": "AP Dhillon", + "artist_mbid": "ed29f721-0d40-4a90-9f25-a91c2ccccd5e", + "join_phrase": ", " + }, + { + "artist_credit_name": "Gurinder Gill", + "artist_mbid": "56743d2c-b315-4d01-a444-fa42088182c0", + "join_phrase": ", " + }, + { + "artist_credit_name": "Intense", + "artist_mbid": "f903e686-a5f4-40e1-8fa8-c8132369c51e", + "join_phrase": "" + } + ], + "caa_id": 31899892362, + "caa_release_mbid": "b6bb2693-5482-40e5-91e2-d87ac4cf896b", + "listen_count": 2, + "release_group_mbid": "db309423-e72d-4f1b-854d-37e0556f2354", + "release_group_name": "Excuses" + }, + { + "artist_mbids": [ + "addca31a-40ae-4152-afb8-a477da5191f0" + ], + "artist_name": "Anuv Jain", + "artists": [ + { + "artist_credit_name": "Anuv Jain", + "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", + "join_phrase": "" + } + ], + "caa_id": 35700820331, + "caa_release_mbid": "bcd47c0b-7ce3-4124-9786-ebe7e7d0867f", + "listen_count": 2, + "release_group_mbid": "b5002117-f086-48a7-9c14-b83b61e32d27", + "release_group_name": "Antariksh" + } + ], + "to_ts": 1739145600, + "total_release_group_count": 57, + "user_id": "holycow23" + } +} + result = {} + for release_group in data["payload"]["release_groups"]: + artist_name = release_group["artist_name"] + listen_count = release_group["listen_count"] + release_group_name = release_group["release_group_name"] + + if artist_name in result: + result[artist_name]["listen_count"] += listen_count + result[artist_name]["albums"].append({"name": release_group_name, "listen_count": listen_count}) + else: + result[artist_name] = { + "name": artist_name, + "listen_count": listen_count, + "albums": [{"name": release_group_name, "listen_count": listen_count}] + } + sorted_data = sorted(result.values(), key=lambda x: x["listen_count"], reverse=True) + return jsonify({"result": sorted_data}) + @stats_api_bp.get("/user//daily-activity") @crossdomain @ratelimit() @@ -1245,7 +1774,7 @@ def year_in_music(user_name: str, year: int = 2024): }) -def _process_user_entity(stats: StatApi[EntityRecord], offset: int, count: int) -> Tuple[list[dict], int]: +def _process_user_entity(stats: StatApi[EntityRecord], offset: int, count: int, entire_range: bool) -> Tuple[list[dict], int]: """ Process the statistics data according to query params Args: @@ -1262,7 +1791,10 @@ def _process_user_entity(stats: StatApi[EntityRecord], offset: int, count: int) count = min(count, MAX_ITEMS_PER_GET) count = count + offset total_entity_count = stats.count - entity_list = [x.dict() for x in stats.data.__root__[offset:count]] + if entire_range: + entity_list = [x.dict() for x in stats.data.__root__] + else: + entity_list = [x.dict() for x in stats.data.__root__[offset:count]] return entity_list, total_entity_count From d0ca02372b86cc80810db73ce2621635bb9aacd4 Mon Sep 17 00:00:00 2001 From: granth23 Date: Tue, 11 Feb 2025 07:53:46 +0530 Subject: [PATCH 02/21] nivo rocks chart bug fix --- .../stats/components/UserArtistActivity.tsx | 6 - listenbrainz/webserver/views/stats_api.py | 468 +----------------- 2 files changed, 1 insertion(+), 473 deletions(-) diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index 997377fe0d..385781315f 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -73,17 +73,11 @@ export default function UserArtistActivity({ React.useEffect(() => { if (rawData && rawData.result.length > 0) { - console.log("rawData", rawData); const processedData = processData(rawData); - console.log("processedData", processedData); setChartData(processedData); } }, [rawData]); - React.useEffect(() => { - console.log("Updated chartData", chartData); - }, [chartData]); - return (
diff --git a/listenbrainz/webserver/views/stats_api.py b/listenbrainz/webserver/views/stats_api.py index 9eb86d63e5..404c676e97 100644 --- a/listenbrainz/webserver/views/stats_api.py +++ b/listenbrainz/webserver/views/stats_api.py @@ -453,473 +453,7 @@ def get_artist_activity(user_name: str): :statuscode 404: User not found :resheader Content-Type: *application/json* """ - # data = _get_entity_stats(user_name, "release_groups", "total_release_group_count") - data = { - "payload": { - "count": 25, - "from_ts": 1738540800, - "last_updated": 1739157484, - "offset": 0, - "range": "week", - "release_groups": [ - { - "artist_mbids": [ - "addca31a-40ae-4152-afb8-a477da5191f0" - ], - "artist_name": "Anuv Jain", - "artists": [ - { - "artist_credit_name": "Anuv Jain", - "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", - "join_phrase": "" - } - ], - "caa_id": 33591541837, - "caa_release_mbid": "67b5a2df-0b21-4b4e-b1ac-1b10cf8e164a", - "listen_count": 12, - "release_group_mbid": "1def5e40-cad4-43de-a774-be7eddf6aa6b", - "release_group_name": "Ocean" - }, - { - "artist_mbids": [ - "93e6118e-7fa8-49f6-9e02-699a1ebce105" - ], - "artist_name": "The Local Train", - "artists": [ - { - "artist_credit_name": "The Local Train", - "artist_mbid": "93e6118e-7fa8-49f6-9e02-699a1ebce105", - "join_phrase": "" - } - ], - "caa_id": 26294866570, - "caa_release_mbid": "072f7802-e9c7-487b-b3b2-07d91ac89b11", - "listen_count": 10, - "release_group_mbid": "4f0ccffd-feeb-4d50-a724-31f0eabbc750", - "release_group_name": "Aalas Ka Pedh" - }, - { - "artist_mbids": [ - "addca31a-40ae-4152-afb8-a477da5191f0" - ], - "artist_name": "Anuv Jain", - "artists": [ - { - "artist_credit_name": "Anuv Jain", - "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", - "join_phrase": "" - } - ], - "caa_id": 33600724905, - "caa_release_mbid": "91099f15-6691-487d-a889-7933e014766b", - "listen_count": 7, - "release_group_mbid": "ecd8bfb6-8f4b-4a79-a7fc-b585474e6a49", - "release_group_name": "Baarishein" - }, - { - "artist_mbids": [ - "addca31a-40ae-4152-afb8-a477da5191f0" - ], - "artist_name": "Anuv Jain", - "artists": [ - { - "artist_credit_name": "Anuv Jain", - "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", - "join_phrase": "" - } - ], - "caa_id": 40637299178, - "caa_release_mbid": "462e52cf-01e1-40c8-8ad5-59dfc519afcf", - "listen_count": 5, - "release_group_mbid": "0fa837e4-ec5c-4cad-99a1-d3f84258f5ab", - "release_group_name": "Jo Tum Mere Ho" - }, - { - "artist_mbids": [ - "addca31a-40ae-4152-afb8-a477da5191f0" - ], - "artist_name": "Anuv Jain", - "artists": [ - { - "artist_credit_name": "Anuv Jain", - "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", - "join_phrase": "" - } - ], - "caa_id": 33599951290, - "caa_release_mbid": "b457bb52-dafd-4042-ab2e-91f252fdc822", - "listen_count": 5, - "release_group_mbid": "39bf6cbc-5d91-4ec6-9aca-961cdff0f8bb", - "release_group_name": "Gul" - }, - { - "artist_mbids": [ - "f931c961-b647-4861-be8c-f47d84a4de51" - ], - "artist_name": "Diljit Dosanjh", - "artists": [ - { - "artist_credit_name": "Diljit Dosanjh", - "artist_mbid": "f931c961-b647-4861-be8c-f47d84a4de51", - "join_phrase": "" - } - ], - "caa_id": 33030341692, - "caa_release_mbid": "f9fc0be7-8b5f-4382-bc2a-182938c8a765", - "listen_count": 5, - "release_group_mbid": "23279dea-32b0-47a5-bc4a-68924262c5ec", - "release_group_name": "Drive Thru" - }, - { - "artist_mbids": [ - "ed29f721-0d40-4a90-9f25-a91c2ccccd5e" - ], - "artist_name": "AP Dhillon", - "artists": [ - { - "artist_credit_name": "AP Dhillon", - "artist_mbid": "ed29f721-0d40-4a90-9f25-a91c2ccccd5e", - "join_phrase": "" - } - ], - "caa_id": 33759408581, - "caa_release_mbid": "87a65c84-4d04-4b61-9687-3df76ba83bb2", - "listen_count": 4, - "release_group_mbid": "a659496c-1093-48af-88d5-773f96179e39", - "release_group_name": "Two Hearts Never Break The Same" - }, - { - "artist_mbids": [], - "artist_name": "Jasleen Royal, Stebin Ben, Vijay Deverakonda, Radhikka Madan, Priya Saraiya, Aditya Sharma", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 4, - "release_group_mbid": None, - "release_group_name": "Sahiba" - }, - { - "artist_mbids": [ - "addca31a-40ae-4152-afb8-a477da5191f0" - ], - "artist_name": "Anuv Jain", - "artists": [ - { - "artist_credit_name": "Anuv Jain", - "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", - "join_phrase": "" - } - ], - "caa_id": 33591219607, - "caa_release_mbid": "1e3e4f14-4139-44b5-a08c-c5beee6e8409", - "listen_count": 4, - "release_group_mbid": "6d04c3a2-0080-4aae-8069-95f0738958fb", - "release_group_name": "Meri Baaton Mein Tu" - }, - { - "artist_mbids": [ - "addca31a-40ae-4152-afb8-a477da5191f0" - ], - "artist_name": "Anuv Jain", - "artists": [ - { - "artist_credit_name": "Anuv Jain", - "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", - "join_phrase": "" - } - ], - "caa_id": 33583528095, - "caa_release_mbid": "429f1274-baba-41b4-a927-4fc6b2b49c35", - "listen_count": 4, - "release_group_mbid": "90aaddd2-65d5-41e8-9259-ece05d0b93db", - "release_group_name": "Mazaak" - }, - { - "artist_mbids": [ - "4a779683-5404-4b90-a0d7-242495158265" - ], - "artist_name": "Karan Aujla", - "artists": [ - { - "artist_credit_name": "Karan Aujla", - "artist_mbid": "4a779683-5404-4b90-a0d7-242495158265", - "join_phrase": "" - } - ], - "caa_id": 36478339379, - "caa_release_mbid": "f0537077-e571-4fa2-a907-8f9168329423", - "listen_count": 4, - "release_group_mbid": "5995145d-f279-48f2-a397-097e101a718a", - "release_group_name": "Making Memories" - }, - { - "artist_mbids": [ - "addca31a-40ae-4152-afb8-a477da5191f0" - ], - "artist_name": "Anuv Jain", - "artists": [ - { - "artist_credit_name": "Anuv Jain", - "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", - "join_phrase": "" - } - ], - "caa_id": 33590899663, - "caa_release_mbid": "04def11c-5b23-4bee-b822-1bcd4786aa4e", - "listen_count": 3, - "release_group_mbid": "992b3483-52e5-4cb3-be44-cf8113da5a62", - "release_group_name": "Riha" - }, - { - "artist_mbids": [ - "addca31a-40ae-4152-afb8-a477da5191f0" - ], - "artist_name": "Anuv Jain", - "artists": [ - { - "artist_credit_name": "Anuv Jain", - "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", - "join_phrase": "" - } - ], - "caa_id": 33599465996, - "caa_release_mbid": "aa89caf2-613d-4f3c-9c5f-4514142d867f", - "listen_count": 3, - "release_group_mbid": "4d45defd-3293-4444-9943-def2711fe093", - "release_group_name": "Mishri" - }, - { - "artist_mbids": [ - "f931c961-b647-4861-be8c-f47d84a4de51" - ], - "artist_name": "Diljit Dosanjh", - "artists": [ - { - "artist_credit_name": "Diljit Dosanjh", - "artist_mbid": "f931c961-b647-4861-be8c-f47d84a4de51", - "join_phrase": "" - } - ], - "caa_id": 37129944334, - "caa_release_mbid": "962f6b5a-d6ca-4c02-b47c-b3752b1f90c5", - "listen_count": 3, - "release_group_mbid": "3920b520-1404-495b-8557-5b2091233a14", - "release_group_name": "Ghost" - }, - { - "artist_mbids": [ - "addca31a-40ae-4152-afb8-a477da5191f0" - ], - "artist_name": "Anuv Jain", - "artists": [ - { - "artist_credit_name": "Anuv Jain", - "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", - "join_phrase": "" - } - ], - "caa_id": 33599116598, - "caa_release_mbid": "ad42e780-12cb-4418-a33a-ad4c2f3bc65e", - "listen_count": 3, - "release_group_mbid": "80b85e66-7fe8-4d93-9a0e-8595e465a713", - "release_group_name": "Alag Aasmaan" - }, - { - "artist_mbids": [ - "93e6118e-7fa8-49f6-9e02-699a1ebce105" - ], - "artist_name": "The Local Train", - "artists": [ - { - "artist_credit_name": "The Local Train", - "artist_mbid": "93e6118e-7fa8-49f6-9e02-699a1ebce105", - "join_phrase": "" - } - ], - "caa_id": 26294968495, - "caa_release_mbid": "852b618b-5c35-4491-9dcd-90794b54d876", - "listen_count": 2, - "release_group_mbid": "f408529f-0e6b-4933-a260-70715b5862f7", - "release_group_name": "Vaaqif" - }, - { - "artist_mbids": [ - "fe51ec18-bab4-4774-b0f5-ff0f175a41a7", - "41416396-17a2-478c-bf1e-209494d5f160" - ], - "artist_name": "Juss & MixSingh", - "artists": [ - { - "artist_credit_name": "Juss", - "artist_mbid": "fe51ec18-bab4-4774-b0f5-ff0f175a41a7", - "join_phrase": " & " - }, - { - "artist_credit_name": "MixSingh", - "artist_mbid": "41416396-17a2-478c-bf1e-209494d5f160", - "join_phrase": "" - } - ], - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 2, - "release_group_mbid": "2415d5c9-650b-4858-82d0-087dd60101fc", - "release_group_name": "Suniyan Suniyan" - }, - { - "artist_mbids": [ - "ed29f721-0d40-4a90-9f25-a91c2ccccd5e" - ], - "artist_name": "AP Dhillon", - "artists": [ - { - "artist_credit_name": "AP Dhillon", - "artist_mbid": "ed29f721-0d40-4a90-9f25-a91c2ccccd5e", - "join_phrase": "" - } - ], - "caa_id": 33215776810, - "caa_release_mbid": "938fd497-d709-4e53-b3bf-545b18d9faf0", - "listen_count": 2, - "release_group_mbid": "284ec2d6-af21-47e9-92e1-4dd44befa85d", - "release_group_name": "Summer High" - }, - { - "artist_mbids": [ - "5d77d4cf-febf-4e5c-b8e7-16ea5ffb0439" - ], - "artist_name": "Aditya Rikhari", - "artists": [ - { - "artist_credit_name": "Aditya Rikhari", - "artist_mbid": "5d77d4cf-febf-4e5c-b8e7-16ea5ffb0439", - "join_phrase": "" - } - ], - "caa_id": 35225071094, - "caa_release_mbid": "dd67ee08-49ef-4508-bf86-8c3eaf69791b", - "listen_count": 2, - "release_group_mbid": "447d85e7-2b6a-4974-9ecb-21f18678dc8c", - "release_group_name": "Samjho Na" - }, - { - "artist_mbids": [ - "1b8f90e7-435b-4a6a-a62d-ea154a8191fe" - ], - "artist_name": "Faheem Abdullah", - "artists": [ - { - "artist_credit_name": "Faheem Abdullah", - "artist_mbid": "1b8f90e7-435b-4a6a-a62d-ea154a8191fe", - "join_phrase": "" - } - ], - "caa_id": 40800551557, - "caa_release_mbid": "9a938666-b5a3-4612-8050-9540a1a5f479", - "listen_count": 2, - "release_group_mbid": "2892401d-a4f8-4791-9329-1a7b5834fe52", - "release_group_name": "Lost;Found" - }, - { - "artist_mbids": [ - "810b210f-b461-4f02-aff2-dff91196147e" - ], - "artist_name": "Prateek Kuhad", - "artists": [ - { - "artist_credit_name": "Prateek Kuhad", - "artist_mbid": "810b210f-b461-4f02-aff2-dff91196147e", - "join_phrase": "" - } - ], - "caa_id": 34528409438, - "caa_release_mbid": "6f1bbe0d-1663-4dd5-947a-8e780610e4c5", - "listen_count": 2, - "release_group_mbid": "f68f3c53-3f64-412a-9b3e-636154d077b0", - "release_group_name": "Kasoor" - }, - { - "artist_mbids": [], - "artist_name": "Aditya Rikhari, Tulsi Kumar, Lijo George-Dj Chetas", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 2, - "release_group_mbid": None, - "release_group_name": "Jaana Samjho Na (From \"Bhool Bhulaiyaa 3\")" - }, - { - "artist_mbids": [ - "f931c961-b647-4861-be8c-f47d84a4de51" - ], - "artist_name": "Diljit Dosanjh", - "artists": [ - { - "artist_credit_name": "Diljit Dosanjh", - "artist_mbid": "f931c961-b647-4861-be8c-f47d84a4de51", - "join_phrase": "" - } - ], - "caa_id": 28031589733, - "caa_release_mbid": "2906b920-a0df-49b3-9669-19f8a1847d20", - "listen_count": 2, - "release_group_mbid": "2fa55494-4806-4425-9e5e-d70e07af4f07", - "release_group_name": "G.O.A.T." - }, - { - "artist_mbids": [ - "ed29f721-0d40-4a90-9f25-a91c2ccccd5e", - "56743d2c-b315-4d01-a444-fa42088182c0", - "f903e686-a5f4-40e1-8fa8-c8132369c51e" - ], - "artist_name": "AP Dhillon, Gurinder Gill, Intense", - "artists": [ - { - "artist_credit_name": "AP Dhillon", - "artist_mbid": "ed29f721-0d40-4a90-9f25-a91c2ccccd5e", - "join_phrase": ", " - }, - { - "artist_credit_name": "Gurinder Gill", - "artist_mbid": "56743d2c-b315-4d01-a444-fa42088182c0", - "join_phrase": ", " - }, - { - "artist_credit_name": "Intense", - "artist_mbid": "f903e686-a5f4-40e1-8fa8-c8132369c51e", - "join_phrase": "" - } - ], - "caa_id": 31899892362, - "caa_release_mbid": "b6bb2693-5482-40e5-91e2-d87ac4cf896b", - "listen_count": 2, - "release_group_mbid": "db309423-e72d-4f1b-854d-37e0556f2354", - "release_group_name": "Excuses" - }, - { - "artist_mbids": [ - "addca31a-40ae-4152-afb8-a477da5191f0" - ], - "artist_name": "Anuv Jain", - "artists": [ - { - "artist_credit_name": "Anuv Jain", - "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", - "join_phrase": "" - } - ], - "caa_id": 35700820331, - "caa_release_mbid": "bcd47c0b-7ce3-4124-9786-ebe7e7d0867f", - "listen_count": 2, - "release_group_mbid": "b5002117-f086-48a7-9c14-b83b61e32d27", - "release_group_name": "Antariksh" - } - ], - "to_ts": 1739145600, - "total_release_group_count": 57, - "user_id": "holycow23" - } -} + data = _get_entity_stats(user_name, "release_groups", "total_release_group_count") result = {} for release_group in data["payload"]["release_groups"]: artist_name = release_group["artist_name"] From c32aecb5b6324e2db7ee8c9a59e32f43e810011f Mon Sep 17 00:00:00 2001 From: granth23 Date: Fri, 14 Feb 2025 11:24:02 +0530 Subject: [PATCH 03/21] db returns fixed --- frontend/js/src/user/stats/UserReports.tsx | 2 +- .../src/user/stats/components/UserArtistActivity.tsx | 4 ---- frontend/js/src/utils/types.d.ts | 5 +++++ listenbrainz/webserver/views/stats_api.py | 12 ++++++++++-- 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/frontend/js/src/user/stats/UserReports.tsx b/frontend/js/src/user/stats/UserReports.tsx index 2bc40fbac5..536b6a4291 100644 --- a/frontend/js/src/user/stats/UserReports.tsx +++ b/frontend/js/src/user/stats/UserReports.tsx @@ -169,7 +169,7 @@ export default function UserReports() {
)} {user && ( -
+
{statsExplanationModalButton}
diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index 385781315f..9578b2a8d8 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -65,10 +65,6 @@ export default function UserArtistActivity({ })) as ChartDataItem[]; }; - type ChartDataItem = { - label: string; - } & Record; - const [chartData, setChartData] = React.useState([]); React.useEffect(() => { diff --git a/frontend/js/src/utils/types.d.ts b/frontend/js/src/utils/types.d.ts index 5954ca759e..b8842a6891 100644 --- a/frontend/js/src/utils/types.d.ts +++ b/frontend/js/src/utils/types.d.ts @@ -288,6 +288,11 @@ declare type UserDailyActivityResponse = { }; }; +declare type ChartDataItem = { + label: string; + [albumName: string]: number | string; +}; + declare type UserArtistActivityResponse = { result: Array<{ name: string; diff --git a/listenbrainz/webserver/views/stats_api.py b/listenbrainz/webserver/views/stats_api.py index 404c676e97..27f48b5e34 100644 --- a/listenbrainz/webserver/views/stats_api.py +++ b/listenbrainz/webserver/views/stats_api.py @@ -453,9 +453,17 @@ def get_artist_activity(user_name: str): :statuscode 404: User not found :resheader Content-Type: *application/json* """ - data = _get_entity_stats(user_name, "release_groups", "total_release_group_count") + user, stats_range = _validate_stats_user_params(user_name) + offset = get_non_negative_param("offset", default=0) + count = get_non_negative_param("count", default=DEFAULT_ITEMS_PER_GET) + stats = db_stats.get(user["id"], "release_groups", stats_range, EntityRecord) + if stats is None: + raise APINoContent('') + + release_group_list, _ = _process_user_entity(stats, offset, count, entire_range=True) + result = {} - for release_group in data["payload"]["release_groups"]: + for release_group in release_group_list: artist_name = release_group["artist_name"] listen_count = release_group["listen_count"] release_group_name = release_group["release_group_name"] From 38d481e6cc0421e79c17fc759bf4a553f504be5e Mon Sep 17 00:00:00 2001 From: granth23 Date: Tue, 18 Feb 2025 03:42:15 +0530 Subject: [PATCH 04/21] bugs fixed amd total artist limit added --- .../src/user/stats/components/UserArtistActivity.tsx | 12 +++++------- frontend/js/src/utils/types.d.ts | 5 ----- listenbrainz/webserver/views/stats_api.py | 8 ++++++-- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index 9578b2a8d8..093c355896 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -14,11 +14,9 @@ export type UserArtistActivityProps = { user: ListenBrainzUser; }; -export type UserArtistActivityState = { - data: UserArtistActivityResponse; - loading: boolean; - errorMessage: string; - hasError: boolean; +export declare type ChartDataItem = { + label: string; + [albumName: string]: number | string; }; export default function UserArtistActivity({ @@ -57,7 +55,7 @@ export default function UserArtistActivity({ return []; } return data.result.map((artist) => ({ - label: artist.name.split(",")[0], + label: artist.name, ...artist.albums.reduce( (acc, album) => ({ ...acc, [album.name]: album.listen_count }), {} as Record @@ -129,7 +127,7 @@ export default function UserArtistActivity({ margin={{ top: 20, right: 80, bottom: 60, left: 80 }} padding={0.2} layout="vertical" - colors={{ scheme: "pink_yellowGreen" }} + colors={{ scheme: "nivo" }} borderColor={{ from: "color", modifiers: [["darker", 1.6]] }} enableLabel={false} /> diff --git a/frontend/js/src/utils/types.d.ts b/frontend/js/src/utils/types.d.ts index b8842a6891..5954ca759e 100644 --- a/frontend/js/src/utils/types.d.ts +++ b/frontend/js/src/utils/types.d.ts @@ -288,11 +288,6 @@ declare type UserDailyActivityResponse = { }; }; -declare type ChartDataItem = { - label: string; - [albumName: string]: number | string; -}; - declare type UserArtistActivityResponse = { result: Array<{ name: string; diff --git a/listenbrainz/webserver/views/stats_api.py b/listenbrainz/webserver/views/stats_api.py index 27f48b5e34..7b24560218 100644 --- a/listenbrainz/webserver/views/stats_api.py +++ b/listenbrainz/webserver/views/stats_api.py @@ -464,7 +464,7 @@ def get_artist_activity(user_name: str): result = {} for release_group in release_group_list: - artist_name = release_group["artist_name"] + artist_name = release_group["artist_name"].split(",")[0] listen_count = release_group["listen_count"] release_group_name = release_group["release_group_name"] @@ -477,8 +477,12 @@ def get_artist_activity(user_name: str): "listen_count": listen_count, "albums": [{"name": release_group_name, "listen_count": listen_count}] } + sorted_data = sorted(result.values(), key=lambda x: x["listen_count"], reverse=True) - return jsonify({"result": sorted_data}) + count = 15 + N = min(count, len(sorted_data)) + + return jsonify({"result": sorted_data[:N]}) @stats_api_bp.get("/user//daily-activity") @crossdomain From 77211899d4866bb16a6c511c1deb98ac2a44524b Mon Sep 17 00:00:00 2001 From: granth23 Date: Tue, 18 Feb 2025 04:21:10 +0530 Subject: [PATCH 05/21] sitewide activity added --- frontend/js/src/user/stats/UserReports.tsx | 10 +- .../stats/components/UserArtistActivity.tsx | 14 +-- frontend/js/src/utils/APIService.ts | 10 +- listenbrainz/webserver/views/stats_api.py | 94 ++++++++++++++++++- 4 files changed, 110 insertions(+), 18 deletions(-) diff --git a/frontend/js/src/user/stats/UserReports.tsx b/frontend/js/src/user/stats/UserReports.tsx index 536b6a4291..fa56f109b2 100644 --- a/frontend/js/src/user/stats/UserReports.tsx +++ b/frontend/js/src/user/stats/UserReports.tsx @@ -168,12 +168,10 @@ export default function UserReports() {
)} - {user && ( -
- {statsExplanationModalButton} - -
- )} +
+ {statsExplanationModalButton} + +
{statsExplanationModalButton} diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index 093c355896..76754b65fc 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -11,7 +11,7 @@ import GlobalAppContext from "../../../utils/GlobalAppContext"; export type UserArtistActivityProps = { range: UserStatsAPIRange; - user: ListenBrainzUser; + user?: ListenBrainzUser; }; export declare type ChartDataItem = { @@ -19,18 +19,18 @@ export declare type ChartDataItem = { [albumName: string]: number | string; }; -export default function UserArtistActivity({ - range, - user, -}: UserArtistActivityProps) { +export default function UserArtistActivity(props: UserArtistActivityProps) { const { APIService } = React.useContext(GlobalAppContext); + // Props + const { user, range } = props; + const { data: loaderData, isLoading: loading } = useQuery({ - queryKey: ["userArtistActivity", user.name, range], + queryKey: ["userArtistActivity", user?.name, range], queryFn: async () => { try { const queryData = await APIService.getUserArtistActivity( - user.name, + user?.name, range ); return { data: queryData, hasError: false, errorMessage: "" }; diff --git a/frontend/js/src/utils/APIService.ts b/frontend/js/src/utils/APIService.ts index 310936cd54..ad4976a94d 100644 --- a/frontend/js/src/utils/APIService.ts +++ b/frontend/js/src/utils/APIService.ts @@ -575,10 +575,16 @@ export default class APIService { }; getUserArtistActivity = async ( - userName: string, + userName?: string, range: UserStatsAPIRange = "all_time" ): Promise => { - const url = `${this.APIBaseURI}/stats/user/${userName}/artist-activity?range=${range}`; + let url; + if (userName) { + url = `${this.APIBaseURI}/stats/user/${userName}/artist-activity`; + } else { + url = `${this.APIBaseURI}/stats/sitewide/artist-activity`; + } + url += `?range=${range}`; const response = await fetch(url); await this.checkStatus(response); if (response.status === 204) { diff --git a/listenbrainz/webserver/views/stats_api.py b/listenbrainz/webserver/views/stats_api.py index 7b24560218..20862ac296 100644 --- a/listenbrainz/webserver/views/stats_api.py +++ b/listenbrainz/webserver/views/stats_api.py @@ -481,7 +481,7 @@ def get_artist_activity(user_name: str): sorted_data = sorted(result.values(), key=lambda x: x["listen_count"], reverse=True) count = 15 N = min(count, len(sorted_data)) - + return jsonify({"result": sorted_data[:N]}) @stats_api_bp.get("/user//daily-activity") @@ -1058,7 +1058,7 @@ def get_sitewide_recording(): return _get_sitewide_stats("recordings") -def _get_sitewide_stats(entity: str): +def _get_sitewide_stats(entity: str, entire_range: bool = False): stats_range = request.args.get("range", default="all_time") if not _is_valid_range(stats_range): raise APIBadRequest(f"Invalid range: {stats_range}") @@ -1072,10 +1072,15 @@ def _get_sitewide_stats(entity: str): count = min(count, MAX_ITEMS_PER_GET) total_entity_count = stats["count"] + + if entire_range: + entity_list = stats["data"] + else: + entity_list = stats["data"][offset:count + offset] return jsonify({ "payload": { - entity: stats["data"][offset:count + offset], + entity: entity_list, "range": stats_range, "offset": offset, "count": total_entity_count, @@ -1159,6 +1164,89 @@ def get_sitewide_listening_activity(): }) +@stats_api_bp.get("/sitewide/artist-activity") +@crossdomain +@ratelimit() +def get_sitewide_artist_activity(): + """ + Get the sitewide artist activity. The daily activity shows the number of listens + submitted by the user for each hour of the day over a period of time. We assume that all listens are in UTC. + + A sample response from the endpoint may look like: + + .. code-block:: json + + { + "payload": { + "from_ts": 1587945600, + "last_updated": 1592807084, + "daily_activity": { + "Monday": [ + { + "hour": 0 + "listen_count": 26, + }, + { + "hour": 1 + "listen_count": 30, + }, + { + "hour": 2 + "listen_count": 4, + }, + "..." + ], + "Tuesday": ["..."], + "..." + }, + "stats_range": "all_time", + "to_ts": 1589155200, + "user_id": "ishaanshah" + } + } + + :param range: Optional, time interval for which statistics should be returned, possible values are + :data:`~data.model.common_stat.ALLOWED_STATISTICS_RANGE`, defaults to ``all_time`` + :type range: ``str`` + :statuscode 200: Successful query, you have data! + :statuscode 204: Statistics for the user haven't been calculated, empty response will be returned + :statuscode 400: Bad request, check ``response['error']`` for more details + :statuscode 404: User not found + :resheader Content-Type: *application/json* + + """ + stats_range = request.args.get("range", default="all_time") + if not _is_valid_range(stats_range): + raise APIBadRequest(f"Invalid range: {stats_range}") + + stats = db_stats.get_sitewide_stats("artists", stats_range) + if stats is None: + raise APINoContent('') + + release_groups_list = stats["data"] + + result = {} + for release_group in release_groups_list: + artist_name = release_group["artist_name"].split(",")[0] + listen_count = release_group["listen_count"] + release_group_name = release_group["release_group_name"] + + if artist_name in result: + result[artist_name]["listen_count"] += listen_count + result[artist_name]["albums"].append({"name": release_group_name, "listen_count": listen_count}) + else: + result[artist_name] = { + "name": artist_name, + "listen_count": listen_count, + "albums": [{"name": release_group_name, "listen_count": listen_count}] + } + + sorted_data = sorted(result.values(), key=lambda x: x["listen_count"], reverse=True) + count = 15 + N = min(count, len(sorted_data)) + return jsonify({"result": sorted_data[:N]}) + + @stats_api_bp.get("/sitewide/artist-map") @crossdomain @ratelimit() From f49def558dbfc36d8cd91ccad5e8e836906f9f9b Mon Sep 17 00:00:00 2001 From: granth23 Date: Tue, 18 Feb 2025 04:44:31 +0530 Subject: [PATCH 06/21] test component added --- .../tests/__mocks__/userArtistActivity.json | 130 ++++++++++++++++++ .../user/stats/UserArtistActivity.test.tsx | 96 +++++++++++++ 2 files changed, 226 insertions(+) create mode 100644 frontend/js/tests/__mocks__/userArtistActivity.json create mode 100644 frontend/js/tests/user/stats/UserArtistActivity.test.tsx diff --git a/frontend/js/tests/__mocks__/userArtistActivity.json b/frontend/js/tests/__mocks__/userArtistActivity.json new file mode 100644 index 0000000000..088881513a --- /dev/null +++ b/frontend/js/tests/__mocks__/userArtistActivity.json @@ -0,0 +1,130 @@ +{ + "result": [ + { + "name": "Ed Sheeran", + "listen_count": 87, + "albums": [ + { "listen_count": 17, "name": "=" }, + { "listen_count": 12, "name": "÷" }, + { "listen_count": 11, "name": "+" }, + { "listen_count": 10, "name": "×" }, + { "listen_count": 16, "name": "No.6 Collaborations Project" }, + { "listen_count": 16, "name": "-" }, + { "listen_count": 3, "name": "Autumn Variations" }, + { "listen_count": 2, "name": "Under the tree" } + ] + }, + { + "name": "AP Dhillon", + "listen_count": 46, + "albums": [ + { "listen_count": 6, "name": "Two Hearts Never Break The Same" }, + { "listen_count": 3, "name": "HIDDEN GEMS" }, + { "listen_count": 8, "name": "Summer High" }, + { "listen_count": 8, "name": "Ma Belle" }, + { "listen_count": 8, "name": "Excuses" }, + { "listen_count": 7, "name": "Insane" }, + { "listen_count": 6, "name": "The Brownprint" } + ] + }, + { + "name": "Diljit Dosanjh", + "listen_count": 27, + "albums": [ + { "listen_count": 7, "name": "Ghost" }, + { "listen_count": 5, "name": "G.O.A.T." }, + { "listen_count": 3, "name": "Drive Thru" }, + { "listen_count": 6, "name": "Love Ya" }, + { "listen_count": 6, "name": "In Love With Diljit Dosanjh" } + ] + }, + { + "name": "Honey Singh", + "listen_count": 21, + "albums": [ + { "listen_count": 21, "name": "International Villager" } + ] + }, + { + "name": "Yo Yo Honey Singh", + "listen_count": 14, + "albums": [ + { "listen_count": 4, "name": "Desi Kalakaar" }, + { "listen_count": 2, "name": "Zorawar" }, + { "listen_count": 8, "name": "Glory" } + ] + }, + { + "name": "Anuv Jain", + "listen_count": 12, + "albums": [ + { "listen_count": 5, "name": "Ocean" }, + { "listen_count": 7, "name": "Baarishein" } + ] + }, + { + "name": "Avvy Sra & Karan Aujla", + "listen_count": 8, + "albums": [ + { "listen_count": 8, "name": "White Brown Black" } + ] + }, + { + "name": "Shubh", + "listen_count": 8, + "albums": [ + { "listen_count": 2, "name": "We Rollin" }, + { "listen_count": 6, "name": "Bandana" } + ] + }, + { + "name": "Pav Dharia", + "listen_count": 8, + "albums": [ + { "listen_count": 8, "name": "Na Ja" } + ] + }, + { + "name": "Vishal Mishra", + "listen_count": 8, + "albums": [ + { "listen_count": 8, "name": "Manjha" } + ] + }, + { + "name": "Aditya Rikhari", + "listen_count": 8, + "albums": [ + { "listen_count": 8, "name": "Jaana Samjho Na (From \"Bhool Bhulaiyaa 3\")" } + ] + }, + { + "name": "Juss & MixSingh", + "listen_count": 7, + "albums": [ + { "listen_count": 7, "name": "Suniyan Suniyan" } + ] + }, + { + "name": "Diljit Dosanjh & Sia", + "listen_count": 7, + "albums": [ + { "listen_count": 7, "name": "Hass Hass" } + ] + }, + { + "name": "Jassa Dhillon", + "listen_count": 6, + "albums": [ + { "listen_count": 6, "name": "VIBIN" } + ] + }, + { + "name": "Jasleen Royal", + "listen_count": 6, + "albums": [ + { "listen_count": 6, "name": "Sahiba" } + ] + } + ] +} diff --git a/frontend/js/tests/user/stats/UserArtistActivity.test.tsx b/frontend/js/tests/user/stats/UserArtistActivity.test.tsx new file mode 100644 index 0000000000..7d09881671 --- /dev/null +++ b/frontend/js/tests/user/stats/UserArtistActivity.test.tsx @@ -0,0 +1,96 @@ +import * as React from "react"; + +import { screen, waitFor } from "@testing-library/react"; +import { SetupServerApi, setupServer } from "msw/node"; +import { http, HttpResponse } from "msw"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import UserArtistActivity, { + UserArtistActivityProps, +} from "../../../src/user/stats/components/UserArtistActivity"; +import * as userArtistActivityResponse from "../../__mocks__/userArtistActivity.json"; +import { renderWithProviders } from "../../test-utils/rtl-test-utils"; + +const userProps: UserArtistActivityProps = { + user: { + name: "foobar", + }, + range: "week", +}; + +jest.mock("@nivo/bar", () => ({ + ...jest.requireActual("@nivo/bar"), + ResponsiveBar: ({ data }: any) => ( +
{JSON.stringify(data)}
+ ), +})); + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + retry: false, + }, + }, +}); + +const reactQueryWrapper = ({ children }: any) => ( + {children} +); + +describe("UserArtistActivity", () => { + let server: SetupServerApi; + beforeAll(() => { + const handlers = [ + http.get("/1/stats/user/foobar/artist-activity", async ({ request }) => { + const url = new URL(request.url); + const range = url.searchParams.get("range"); + + switch (range) { + case "week": + return HttpResponse.json(userArtistActivityResponse); + default: + return HttpResponse.json( + { error: "Failed to fetch data" }, + { status: 500 } + ); + } + }), + ]; + server = setupServer(...handlers); + server.listen(); + }); + afterEach(() => { + queryClient.cancelQueries(); + queryClient.clear(); + }); + afterAll(() => { + server.close(); + }); + + it("renders correctly", async () => { + renderWithProviders( + , + {}, + { + wrapper: reactQueryWrapper, + } + ); + + await waitFor(() => { + expect(screen.getByTestId("ResponsiveBar")).toBeInTheDocument(); + }); + }); + + it("displays error message when API call fails", async () => { + renderWithProviders( + , + {}, + { + wrapper: reactQueryWrapper, + } + ); + + await waitFor(() => { + expect(screen.getByText("Failed to fetch data")).toBeInTheDocument(); + }); + }); +}); From 472bc57143f9d559cafec22d6d318236080924ea Mon Sep 17 00:00:00 2001 From: granth23 Date: Tue, 18 Feb 2025 13:01:40 +0530 Subject: [PATCH 07/21] test added --- .../user/stats/UserArtistActivity.test.tsx | 27 ++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/frontend/js/tests/user/stats/UserArtistActivity.test.tsx b/frontend/js/tests/user/stats/UserArtistActivity.test.tsx index 7d09881671..8546fe8ba2 100644 --- a/frontend/js/tests/user/stats/UserArtistActivity.test.tsx +++ b/frontend/js/tests/user/stats/UserArtistActivity.test.tsx @@ -17,6 +17,10 @@ const userProps: UserArtistActivityProps = { range: "week", }; +const sitewideProps: UserArtistActivityProps = { + range: "week", +}; + jest.mock("@nivo/bar", () => ({ ...jest.requireActual("@nivo/bar"), ResponsiveBar: ({ data }: any) => ( @@ -36,7 +40,10 @@ const reactQueryWrapper = ({ children }: any) => ( {children} ); -describe("UserArtistActivity", () => { +describe.each([ + ["User Stats", userProps], + ["Sitewide Stats", sitewideProps], +])("%s", (name, props) => { let server: SetupServerApi; beforeAll(() => { const handlers = [ @@ -44,6 +51,20 @@ describe("UserArtistActivity", () => { const url = new URL(request.url); const range = url.searchParams.get("range"); + switch (range) { + case "week": + return HttpResponse.json(userArtistActivityResponse); + default: + return HttpResponse.json( + { error: "Failed to fetch data" }, + { status: 500 } + ); + } + }), + http.get("/1/stats/sitewide/artist-activity", async ({ request }) => { + const url = new URL(request.url); + const range = url.searchParams.get("range"); + switch (range) { case "week": return HttpResponse.json(userArtistActivityResponse); @@ -68,7 +89,7 @@ describe("UserArtistActivity", () => { it("renders correctly", async () => { renderWithProviders( - , + , {}, { wrapper: reactQueryWrapper, @@ -82,7 +103,7 @@ describe("UserArtistActivity", () => { it("displays error message when API call fails", async () => { renderWithProviders( - , + , {}, { wrapper: reactQueryWrapper, From e6108a9be19ef9a1a4f7856ae3c7b4d6b2192785 Mon Sep 17 00:00:00 2001 From: granth23 Date: Mon, 24 Feb 2025 02:05:01 +0530 Subject: [PATCH 08/21] album redirect added --- .../stats/components/UserArtistActivity.tsx | 23 ++++++ frontend/js/src/utils/types.d.ts | 1 + .../user/stats/UserArtistActivity.test.tsx | 6 +- listenbrainz/webserver/views/stats_api.py | 81 +++++++++---------- 4 files changed, 64 insertions(+), 47 deletions(-) diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index 76754b65fc..8083e68562 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -65,6 +65,20 @@ export default function UserArtistActivity(props: UserArtistActivityProps) { const [chartData, setChartData] = React.useState([]); + const albumRedirectMapping = React.useMemo(() => { + const mapping: Record = {}; + if (rawData && rawData.result) { + rawData.result.forEach((artist) => { + artist.albums.forEach((album) => { + if (album.release_group_mbid) { + mapping[`${artist.name}-${album.name}`] = album.release_group_mbid; + } + }); + }); + } + return mapping; + }, [rawData]); + React.useEffect(() => { if (rawData && rawData.result.length > 0) { const processedData = processData(rawData); @@ -130,6 +144,15 @@ export default function UserArtistActivity(props: UserArtistActivityProps) { colors={{ scheme: "nivo" }} borderColor={{ from: "color", modifiers: [["darker", 1.6]] }} enableLabel={false} + onClick={(barData, event) => { + const albumName = barData.id; + const artistName = barData.indexValue; + const releaseMbid = + albumRedirectMapping[`${artistName}-${albumName}`]; + if (releaseMbid) { + window.location.href = `/album/${releaseMbid}`; + } + }} /> diff --git a/frontend/js/src/utils/types.d.ts b/frontend/js/src/utils/types.d.ts index 5954ca759e..ced519152c 100644 --- a/frontend/js/src/utils/types.d.ts +++ b/frontend/js/src/utils/types.d.ts @@ -295,6 +295,7 @@ declare type UserArtistActivityResponse = { albums: Array<{ name: string; listen_count: number; + release_group_mbid: string; }>; }>; }; diff --git a/frontend/js/tests/user/stats/UserArtistActivity.test.tsx b/frontend/js/tests/user/stats/UserArtistActivity.test.tsx index 8546fe8ba2..7571507efc 100644 --- a/frontend/js/tests/user/stats/UserArtistActivity.test.tsx +++ b/frontend/js/tests/user/stats/UserArtistActivity.test.tsx @@ -23,9 +23,7 @@ const sitewideProps: UserArtistActivityProps = { jest.mock("@nivo/bar", () => ({ ...jest.requireActual("@nivo/bar"), - ResponsiveBar: ({ data }: any) => ( -
{JSON.stringify(data)}
- ), + ResponsiveBar: ({ children }: any) => children({ width: 400, height: 400 }), })); const queryClient = new QueryClient({ @@ -97,7 +95,7 @@ describe.each([ ); await waitFor(() => { - expect(screen.getByTestId("ResponsiveBar")).toBeInTheDocument(); + expect(screen.getByTestId("user-artist-activity")).toBeInTheDocument(); }); }); diff --git a/listenbrainz/webserver/views/stats_api.py b/listenbrainz/webserver/views/stats_api.py index 20862ac296..1ee205f5e6 100644 --- a/listenbrainz/webserver/views/stats_api.py +++ b/listenbrainz/webserver/views/stats_api.py @@ -408,6 +408,39 @@ def get_listening_activity(user_name: str): "last_updated": stats.last_updated }}) +def _get_artist_activity(release_groups_list): + result = {} + for release_group in release_groups_list: + artist_name = release_group["artist_name"].split(",")[0] + listen_count = release_group["listen_count"] + release_group_name = release_group["release_group_name"] + + if artist_name in result: + result[artist_name]["listen_count"] += listen_count + for album in result[artist_name]["albums"]: + if album["name"] == release_group_name: + album["listen_count"] += listen_count + break + else: + result[artist_name]["albums"].append({"name": release_group_name, "listen_count": listen_count, "release_group_mbid": release_group["release_group_mbid"]}) + else: + if release_group["release_group_mbid"]: + result[artist_name] = { + "name": artist_name, + "listen_count": listen_count, + "albums": [{"name": release_group_name, "listen_count": listen_count, "release_group_mbid": release_group["release_group_mbid"]}] + } + else: + result[artist_name] = { + "name": artist_name, + "listen_count": listen_count, + "albums": [{"name": release_group_name, "listen_count": listen_count}] + } + + sorted_data = sorted(result.values(), key=lambda x: x["listen_count"], reverse=True) + count = 15 + N = min(count, len(sorted_data)) + return sorted_data[:N] @stats_api_bp.get("/user//artist-activity") @crossdomain @@ -460,29 +493,10 @@ def get_artist_activity(user_name: str): if stats is None: raise APINoContent('') - release_group_list, _ = _process_user_entity(stats, offset, count, entire_range=True) + release_groups_list, _ = _process_user_entity(stats, offset, count, entire_range=True) + result = _get_artist_activity(release_groups_list) + return jsonify({"result": result}) - result = {} - for release_group in release_group_list: - artist_name = release_group["artist_name"].split(",")[0] - listen_count = release_group["listen_count"] - release_group_name = release_group["release_group_name"] - - if artist_name in result: - result[artist_name]["listen_count"] += listen_count - result[artist_name]["albums"].append({"name": release_group_name, "listen_count": listen_count}) - else: - result[artist_name] = { - "name": artist_name, - "listen_count": listen_count, - "albums": [{"name": release_group_name, "listen_count": listen_count}] - } - - sorted_data = sorted(result.values(), key=lambda x: x["listen_count"], reverse=True) - count = 15 - N = min(count, len(sorted_data)) - - return jsonify({"result": sorted_data[:N]}) @stats_api_bp.get("/user//daily-activity") @crossdomain @@ -1224,27 +1238,8 @@ def get_sitewide_artist_activity(): raise APINoContent('') release_groups_list = stats["data"] - - result = {} - for release_group in release_groups_list: - artist_name = release_group["artist_name"].split(",")[0] - listen_count = release_group["listen_count"] - release_group_name = release_group["release_group_name"] - - if artist_name in result: - result[artist_name]["listen_count"] += listen_count - result[artist_name]["albums"].append({"name": release_group_name, "listen_count": listen_count}) - else: - result[artist_name] = { - "name": artist_name, - "listen_count": listen_count, - "albums": [{"name": release_group_name, "listen_count": listen_count}] - } - - sorted_data = sorted(result.values(), key=lambda x: x["listen_count"], reverse=True) - count = 15 - N = min(count, len(sorted_data)) - return jsonify({"result": sorted_data[:N]}) + result = _get_artist_activity(release_groups_list) + return jsonify({"result": result}) @stats_api_bp.get("/sitewide/artist-map") From d7b6c14ef555aa79c24ea2c08c065c10fc526f8d Mon Sep 17 00:00:00 2001 From: Granth Bagadia Date: Wed, 12 Mar 2025 21:49:44 +0530 Subject: [PATCH 09/21] all artists names included --- listenbrainz/webserver/views/stats_api.py | 49 ++++++++++++----------- 1 file changed, 25 insertions(+), 24 deletions(-) diff --git a/listenbrainz/webserver/views/stats_api.py b/listenbrainz/webserver/views/stats_api.py index 1ee205f5e6..641f0fe298 100644 --- a/listenbrainz/webserver/views/stats_api.py +++ b/listenbrainz/webserver/views/stats_api.py @@ -411,31 +411,32 @@ def get_listening_activity(user_name: str): def _get_artist_activity(release_groups_list): result = {} for release_group in release_groups_list: - artist_name = release_group["artist_name"].split(",")[0] - listen_count = release_group["listen_count"] - release_group_name = release_group["release_group_name"] - - if artist_name in result: - result[artist_name]["listen_count"] += listen_count - for album in result[artist_name]["albums"]: - if album["name"] == release_group_name: - album["listen_count"] += listen_count - break - else: - result[artist_name]["albums"].append({"name": release_group_name, "listen_count": listen_count, "release_group_mbid": release_group["release_group_mbid"]}) - else: - if release_group["release_group_mbid"]: - result[artist_name] = { - "name": artist_name, - "listen_count": listen_count, - "albums": [{"name": release_group_name, "listen_count": listen_count, "release_group_mbid": release_group["release_group_mbid"]}] - } + artist_names = release_group["artist_name"].split(",") + for artist_name in artist_names: + listen_count = release_group["listen_count"] + release_group_name = release_group["release_group_name"] + + if artist_name in result: + result[artist_name]["listen_count"] += listen_count + for album in result[artist_name]["albums"]: + if album["name"] == release_group_name: + album["listen_count"] += listen_count + break + else: + result[artist_name]["albums"].append({"name": release_group_name, "listen_count": listen_count, "release_group_mbid": release_group["release_group_mbid"]}) else: - result[artist_name] = { - "name": artist_name, - "listen_count": listen_count, - "albums": [{"name": release_group_name, "listen_count": listen_count}] - } + if release_group["release_group_mbid"]: + result[artist_name] = { + "name": artist_name, + "listen_count": listen_count, + "albums": [{"name": release_group_name, "listen_count": listen_count, "release_group_mbid": release_group["release_group_mbid"]}] + } + else: + result[artist_name] = { + "name": artist_name, + "listen_count": listen_count, + "albums": [{"name": release_group_name, "listen_count": listen_count}] + } sorted_data = sorted(result.values(), key=lambda x: x["listen_count"], reverse=True) count = 15 From 79e5785772c34dc594e1267c72d48eb2b9b2bdfb Mon Sep 17 00:00:00 2001 From: Granth Bagadia Date: Thu, 13 Mar 2025 11:55:58 +0530 Subject: [PATCH 10/21] text-wrap and better optimization --- .../stats/components/UserArtistActivity.tsx | 47 +++++++++++++++---- listenbrainz/webserver/views/stats_api.py | 44 ++++++++--------- 2 files changed, 59 insertions(+), 32 deletions(-) diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index 8083e68562..1e52b34a42 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -54,15 +54,20 @@ export default function UserArtistActivity(props: UserArtistActivityProps) { if (!data || !data.result || data.result.length === 0) { return []; } - return data.result.map((artist) => ({ - label: artist.name, - ...artist.albums.reduce( - (acc, album) => ({ ...acc, [album.name]: album.listen_count }), - {} as Record - ), - })) as ChartDataItem[]; + return data.result.map((artist) => { + let wrappedLabel = artist.name.replace(/(.{14})/g, "$1-\n"); + if (wrappedLabel.endsWith("-\n")) { + wrappedLabel = wrappedLabel.slice(0, -2); // Remove the last hyphen and keep the newline + } + return { + label: wrappedLabel, + ...artist.albums.reduce( + (acc, album) => ({ ...acc, [album.name]: album.listen_count }), + {} as Record + ), + }; + }) as ChartDataItem[]; }; - const [chartData, setChartData] = React.useState([]); const albumRedirectMapping = React.useMemo(() => { @@ -144,6 +149,32 @@ export default function UserArtistActivity(props: UserArtistActivityProps) { colors={{ scheme: "nivo" }} borderColor={{ from: "color", modifiers: [["darker", 1.6]] }} enableLabel={false} + axisBottom={{ + tickSize: 5, + tickPadding: 5, + tickRotation: 0, + renderTick: (tick) => ( + + {tick.value + .split("\n") + .map((line: string, i: number) => ( + + {line} + + ))} + + ), + }} onClick={(barData, event) => { const albumName = barData.id; const artistName = barData.indexValue; diff --git a/listenbrainz/webserver/views/stats_api.py b/listenbrainz/webserver/views/stats_api.py index 641f0fe298..31b24a9cf1 100644 --- a/listenbrainz/webserver/views/stats_api.py +++ b/listenbrainz/webserver/views/stats_api.py @@ -409,34 +409,30 @@ def get_listening_activity(user_name: str): }}) def _get_artist_activity(release_groups_list): - result = {} + result = defaultdict(lambda: {"listen_count": 0, "albums": {}}) + for release_group in release_groups_list: artist_names = release_group["artist_name"].split(",") + listen_count = release_group["listen_count"] + release_group_name = release_group["release_group_name"] + release_group_mbid = release_group.get("release_group_mbid") + for artist_name in artist_names: - listen_count = release_group["listen_count"] - release_group_name = release_group["release_group_name"] - - if artist_name in result: - result[artist_name]["listen_count"] += listen_count - for album in result[artist_name]["albums"]: - if album["name"] == release_group_name: - album["listen_count"] += listen_count - break - else: - result[artist_name]["albums"].append({"name": release_group_name, "listen_count": listen_count, "release_group_mbid": release_group["release_group_mbid"]}) + artist_entry = result[artist_name] + artist_entry["listen_count"] += listen_count + + if release_group_name in artist_entry["albums"]: + artist_entry["albums"][release_group_name]["listen_count"] += listen_count else: - if release_group["release_group_mbid"]: - result[artist_name] = { - "name": artist_name, - "listen_count": listen_count, - "albums": [{"name": release_group_name, "listen_count": listen_count, "release_group_mbid": release_group["release_group_mbid"]}] - } - else: - result[artist_name] = { - "name": artist_name, - "listen_count": listen_count, - "albums": [{"name": release_group_name, "listen_count": listen_count}] - } + artist_entry["albums"][release_group_name] = { + "name": release_group_name, + "listen_count": listen_count, + "release_group_mbid": release_group_mbid, + } + + for artist_name, artist_data in result.items(): + artist_data["name"] = artist_name + artist_data["albums"] = list(artist_data["albums"].values()) sorted_data = sorted(result.values(), key=lambda x: x["listen_count"], reverse=True) count = 15 From eee1df39437b5dd9e7b9e2f1ecf94a0931b61ce9 Mon Sep 17 00:00:00 2001 From: Granth Bagadia Date: Thu, 13 Mar 2025 12:10:30 +0530 Subject: [PATCH 11/21] key-index bug fix --- frontend/js/src/user/stats/components/UserArtistActivity.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index 1e52b34a42..c5888fff50 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -159,7 +159,7 @@ export default function UserArtistActivity(props: UserArtistActivityProps) { .split("\n") .map((line: string, i: number) => ( Date: Thu, 13 Mar 2025 20:23:05 +0530 Subject: [PATCH 12/21] Rotation added to artist names --- .../stats/components/UserArtistActivity.tsx | 9 +- listenbrainz/webserver/views/stats_api.py | 379 +++++++++++++++++- 2 files changed, 376 insertions(+), 12 deletions(-) diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index c5888fff50..169485cd41 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -143,7 +143,7 @@ export default function UserArtistActivity(props: UserArtistActivityProps) { ) )} indexBy="label" - margin={{ top: 20, right: 80, bottom: 60, left: 80 }} + margin={{ top: 20, right: 80, bottom: 80, left: 80 }} padding={0.2} layout="vertical" colors={{ scheme: "nivo" }} @@ -152,7 +152,7 @@ export default function UserArtistActivity(props: UserArtistActivityProps) { axisBottom={{ tickSize: 5, tickPadding: 5, - tickRotation: 0, + tickRotation: -45, renderTick: (tick) => ( {tick.value @@ -162,11 +162,12 @@ export default function UserArtistActivity(props: UserArtistActivityProps) { key={line} x={0} y={10 + i * 15} - textAnchor="middle" - dominantBaseline="hanging" + textAnchor="end" + dominantBaseline="middle" style={{ fontSize: 10, fill: "#000", + transform: `rotate(-45deg)`, }} > {line} diff --git a/listenbrainz/webserver/views/stats_api.py b/listenbrainz/webserver/views/stats_api.py index 9a42b5017b..d927219103 100644 --- a/listenbrainz/webserver/views/stats_api.py +++ b/listenbrainz/webserver/views/stats_api.py @@ -483,14 +483,377 @@ def get_artist_activity(user_name: str): :statuscode 404: User not found :resheader Content-Type: *application/json* """ - user, stats_range = _validate_stats_user_params(user_name) - offset = get_non_negative_param("offset", default=0) - count = get_non_negative_param("count", default=DEFAULT_ITEMS_PER_GET) - stats = db_stats.get(user["id"], "release_groups", stats_range, EntityRecord) - if stats is None: - raise APINoContent('') - - release_groups_list, _ = _process_user_entity(stats, offset, count, entire_range=True) + # user, stats_range = _validate_stats_user_params(user_name) + # offset = get_non_negative_param("offset", default=0) + # count = get_non_negative_param("count", default=DEFAULT_ITEMS_PER_GET) + # stats = db_stats.get(user["id"], "release_groups", stats_range, EntityRecord) + # if stats is None: + # raise APINoContent('') + + # release_groups_list, _ = _process_user_entity(stats, offset, count, entire_range=True) + temp_data = { + "payload": { + "count": 25, + "from_ts": 1740960000, + "last_updated": 1741695262, + "offset": 0, + "range": "week", + "release_groups": [ + { + "artist_mbids": [ + "ed29f721-0d40-4a90-9f25-a91c2ccccd5e" + ], + "artist_name": "AP Dhillon", + "artists": [ + { + "artist_credit_name": "AP Dhillon", + "artist_mbid": "ed29f721-0d40-4a90-9f25-a91c2ccccd5e", + "join_phrase": "" + } + ], + "caa_id": 33759408581, + "caa_release_mbid": "87a65c84-4d04-4b61-9687-3df76ba83bb2", + "listen_count": 4, + "release_group_mbid": "a659496c-1093-48af-88d5-773f96179e39", + "release_group_name": "Two Hearts Never Break The Same" + }, + { + "artist_mbids": [ + "c86a5d1b-8b93-4844-8003-5dedce42e1cb", + "4c73b58a-fb2f-49e3-aaa6-3fcb4a2797f3", + "eb71df54-9a3b-4c46-9ace-be6b61cc49b8" + ], + "artist_name": "Niladri Kumar, Joi Barua & Alif", + "artists": [ + { + "artist_credit_name": "Niladri Kumar", + "artist_mbid": "c86a5d1b-8b93-4844-8003-5dedce42e1cb", + "join_phrase": ", " + }, + { + "artist_credit_name": "Joi Barua", + "artist_mbid": "4c73b58a-fb2f-49e3-aaa6-3fcb4a2797f3", + "join_phrase": " & " + }, + { + "artist_credit_name": "Alif", + "artist_mbid": "eb71df54-9a3b-4c46-9ace-be6b61cc49b8", + "join_phrase": "" + } + ], + "caa_id": 37441982305, + "caa_release_mbid": "57c39828-cccd-455f-a96e-f783a8f7835c", + "listen_count": 3, + "release_group_mbid": "d6698782-35fc-46b4-b744-c346e83ca7ee", + "release_group_name": "Laila Majnu" + }, + { + "artist_mbids": [ + "6113dac8-14b5-435a-b628-10cf8193a0b1" + ], + "artist_name": "Harnoor", + "artists": [ + { + "artist_credit_name": "Harnoor", + "artist_mbid": "6113dac8-14b5-435a-b628-10cf8193a0b1", + "join_phrase": "" + } + ], + "caa_id": 31997791614, + "caa_release_mbid": "1969cf4e-e6bc-464b-9dd1-ae99e5e14bf6", + "listen_count": 2, + "release_group_mbid": "37d2a507-dd56-41d4-925a-0c2c160be711", + "release_group_name": "Waalian" + }, + { + "artist_mbids": [ + "bda9891c-f6a4-442b-8f1c-65429c3347d8" + ], + "artist_name": "King", + "artists": [ + { + "artist_credit_name": "King", + "artist_mbid": "bda9891c-f6a4-442b-8f1c-65429c3347d8", + "join_phrase": "" + } + ], + "caa_id": 34624858128, + "caa_release_mbid": "18f3b5a6-6097-49a5-87ee-173532dbe1a1", + "listen_count": 2, + "release_group_mbid": "3c0546ed-b263-4f1b-931b-8d75c1bc5b2a", + "release_group_name": "The Carnival" + }, + { + "artist_mbids": [ + "ffabe84e-ec03-4e28-8671-1847224ab074" + ], + "artist_name": "Josh Brar", + "artists": [ + { + "artist_credit_name": "Josh Brar", + "artist_mbid": "ffabe84e-ec03-4e28-8671-1847224ab074", + "join_phrase": "" + } + ], + "caa_id": 40714612761, + "caa_release_mbid": "12acbd24-b33f-4c46-8798-6b32a740be3b", + "listen_count": 2, + "release_group_mbid": "b3b91eca-7890-4644-858d-4392060978e4", + "release_group_name": "Tere Bina Na Guzara E" + }, + { + "artist_mbids": [ + "5d77d4cf-febf-4e5c-b8e7-16ea5ffb0439" + ], + "artist_name": "Aditya Rikhari", + "artists": [ + { + "artist_credit_name": "Aditya Rikhari", + "artist_mbid": "5d77d4cf-febf-4e5c-b8e7-16ea5ffb0439", + "join_phrase": "" + } + ], + "caa_id": 35225071094, + "caa_release_mbid": "dd67ee08-49ef-4508-bf86-8c3eaf69791b", + "listen_count": 2, + "release_group_mbid": "447d85e7-2b6a-4974-9ecb-21f18678dc8c", + "release_group_name": "Samjho Na" + }, + { + "artist_mbids": [], + "artist_name": "Majid Unpeek, Lola", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 2, + "release_group_mbid": None, + "release_group_name": "Nion Nihon Nights" + }, + { + "artist_mbids": [ + "addca31a-40ae-4152-afb8-a477da5191f0" + ], + "artist_name": "Anuv Jain", + "artists": [ + { + "artist_credit_name": "Anuv Jain", + "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", + "join_phrase": "" + } + ], + "caa_id": 33591219607, + "caa_release_mbid": "1e3e4f14-4139-44b5-a08c-c5beee6e8409", + "listen_count": 2, + "release_group_mbid": "6d04c3a2-0080-4aae-8069-95f0738958fb", + "release_group_name": "Meri Baaton Mein Tu" + }, + { + "artist_mbids": [ + "985a6779-0e8a-4e52-9aca-597e2e65ab13", + "32560f6d-f87f-4475-a6fb-7c302036aaf1", + "50fd3c55-9c32-490c-bfd5-679208483901", + "27a58074-387a-4f9d-ab4f-1655158cb10f", + "93622908-0806-4173-94c1-9e42597af011" + ], + "artist_name": "Darshan Raval, Jonita Gandhi, Rochak Kohli, Gurpreet Saini & Sonu Nigam", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 2, + "release_group_mbid": None, + "release_group_name": "Ishq Vishk Rebound (Original Motion Picture Soundtrack)" + }, + { + "artist_mbids": [ + "f931c961-b647-4861-be8c-f47d84a4de51" + ], + "artist_name": "Diljit Dosanjh", + "artists": [ + { + "artist_credit_name": "Diljit Dosanjh", + "artist_mbid": "f931c961-b647-4861-be8c-f47d84a4de51", + "join_phrase": "" + } + ], + "caa_id": 37129944334, + "caa_release_mbid": "962f6b5a-d6ca-4c02-b47c-b3752b1f90c5", + "listen_count": 2, + "release_group_mbid": "3920b520-1404-495b-8557-5b2091233a14", + "release_group_name": "Ghost" + }, + { + "artist_mbids": [], + "artist_name": "Darshan Raval", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 2, + "release_group_mbid": None, + "release_group_name": "Ek Tarfa" + }, + { + "artist_mbids": [], + "artist_name": "Pritam, Arijit Singh, Irshad Kamil", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 2, + "release_group_mbid": None, + "release_group_name": "Dunki" + }, + { + "artist_mbids": [ + "f931c961-b647-4861-be8c-f47d84a4de51" + ], + "artist_name": "Diljit Dosanjh", + "artists": [ + { + "artist_credit_name": "Diljit Dosanjh", + "artist_mbid": "f931c961-b647-4861-be8c-f47d84a4de51", + "join_phrase": "" + } + ], + "caa_id": 33030341692, + "caa_release_mbid": "f9fc0be7-8b5f-4382-bc2a-182938c8a765", + "listen_count": 2, + "release_group_mbid": "23279dea-32b0-47a5-bc4a-68924262c5ec", + "release_group_name": "Drive Thru" + }, + { + "artist_mbids": [ + "ed3f4831-e3e0-4dc0-9381-f5649e9df221" + ], + "artist_name": "Arijit Singh", + "artists": [ + { + "artist_credit_name": "Arijit Singh", + "artist_mbid": "ed3f4831-e3e0-4dc0-9381-f5649e9df221", + "join_phrase": "" + } + ], + "caa_id": 40653893324, + "caa_release_mbid": "3e063d04-3ce6-42f3-af59-2725af2d4c9d", + "listen_count": 2, + "release_group_mbid": "a4bd61ea-eadb-4f68-b552-482490462b2a", + "release_group_name": "Arijit\u2019s Aura" + }, + { + "artist_mbids": [], + "artist_name": "LVTA", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 1, + "release_group_mbid": None, + "release_group_name": "wicked" + }, + { + "artist_mbids": [], + "artist_name": "yellofish", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 1, + "release_group_mbid": None, + "release_group_name": "what\u00b4s my name" + }, + { + "artist_mbids": [], + "artist_name": "LVTA", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 1, + "release_group_mbid": None, + "release_group_name": "until we meet again" + }, + { + "artist_mbids": [], + "artist_name": "Lunar Lullaby & yorukaze", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 1, + "release_group_mbid": None, + "release_group_name": "traffic" + }, + { + "artist_mbids": [], + "artist_name": "chanchito estafa", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 1, + "release_group_mbid": None, + "release_group_name": "the house cat" + }, + { + "artist_mbids": [], + "artist_name": "Kaptain Kola", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 1, + "release_group_mbid": None, + "release_group_name": "take it back" + }, + { + "artist_mbids": [], + "artist_name": "Lunar Lullaby & yorukaze", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 1, + "release_group_mbid": None, + "release_group_name": "sweet" + }, + { + "artist_mbids": [], + "artist_name": "slowburn", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 1, + "release_group_mbid": None, + "release_group_name": "still images" + }, + { + "artist_mbids": [], + "artist_name": "LVTA", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 1, + "release_group_mbid": None, + "release_group_name": "silver sky" + }, + { + "artist_mbids": [], + "artist_name": "J0pie", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 1, + "release_group_mbid": None, + "release_group_name": "signs of winter" + }, + { + "artist_mbids": [], + "artist_name": "Bcalm, Dryden", + "artists": None, + "caa_id": None, + "caa_release_mbid": None, + "listen_count": 1, + "release_group_mbid": None, + "release_group_name": "seasons" + } + ], + "to_ts": 1741564800, + "total_release_group_count": 194, + "user_id": "holycow23" + } +} + release_groups_list = temp_data["payload"]["release_groups"] result = _get_artist_activity(release_groups_list) return jsonify({"result": result}) From 1991813bdeb5db9a4d5623aedda87683a6e76fb7 Mon Sep 17 00:00:00 2001 From: Granth Bagadia Date: Thu, 13 Mar 2025 20:26:04 +0530 Subject: [PATCH 13/21] minor bug fix --- listenbrainz/webserver/views/stats_api.py | 379 +--------------------- 1 file changed, 8 insertions(+), 371 deletions(-) diff --git a/listenbrainz/webserver/views/stats_api.py b/listenbrainz/webserver/views/stats_api.py index d927219103..9a42b5017b 100644 --- a/listenbrainz/webserver/views/stats_api.py +++ b/listenbrainz/webserver/views/stats_api.py @@ -483,377 +483,14 @@ def get_artist_activity(user_name: str): :statuscode 404: User not found :resheader Content-Type: *application/json* """ - # user, stats_range = _validate_stats_user_params(user_name) - # offset = get_non_negative_param("offset", default=0) - # count = get_non_negative_param("count", default=DEFAULT_ITEMS_PER_GET) - # stats = db_stats.get(user["id"], "release_groups", stats_range, EntityRecord) - # if stats is None: - # raise APINoContent('') - - # release_groups_list, _ = _process_user_entity(stats, offset, count, entire_range=True) - temp_data = { - "payload": { - "count": 25, - "from_ts": 1740960000, - "last_updated": 1741695262, - "offset": 0, - "range": "week", - "release_groups": [ - { - "artist_mbids": [ - "ed29f721-0d40-4a90-9f25-a91c2ccccd5e" - ], - "artist_name": "AP Dhillon", - "artists": [ - { - "artist_credit_name": "AP Dhillon", - "artist_mbid": "ed29f721-0d40-4a90-9f25-a91c2ccccd5e", - "join_phrase": "" - } - ], - "caa_id": 33759408581, - "caa_release_mbid": "87a65c84-4d04-4b61-9687-3df76ba83bb2", - "listen_count": 4, - "release_group_mbid": "a659496c-1093-48af-88d5-773f96179e39", - "release_group_name": "Two Hearts Never Break The Same" - }, - { - "artist_mbids": [ - "c86a5d1b-8b93-4844-8003-5dedce42e1cb", - "4c73b58a-fb2f-49e3-aaa6-3fcb4a2797f3", - "eb71df54-9a3b-4c46-9ace-be6b61cc49b8" - ], - "artist_name": "Niladri Kumar, Joi Barua & Alif", - "artists": [ - { - "artist_credit_name": "Niladri Kumar", - "artist_mbid": "c86a5d1b-8b93-4844-8003-5dedce42e1cb", - "join_phrase": ", " - }, - { - "artist_credit_name": "Joi Barua", - "artist_mbid": "4c73b58a-fb2f-49e3-aaa6-3fcb4a2797f3", - "join_phrase": " & " - }, - { - "artist_credit_name": "Alif", - "artist_mbid": "eb71df54-9a3b-4c46-9ace-be6b61cc49b8", - "join_phrase": "" - } - ], - "caa_id": 37441982305, - "caa_release_mbid": "57c39828-cccd-455f-a96e-f783a8f7835c", - "listen_count": 3, - "release_group_mbid": "d6698782-35fc-46b4-b744-c346e83ca7ee", - "release_group_name": "Laila Majnu" - }, - { - "artist_mbids": [ - "6113dac8-14b5-435a-b628-10cf8193a0b1" - ], - "artist_name": "Harnoor", - "artists": [ - { - "artist_credit_name": "Harnoor", - "artist_mbid": "6113dac8-14b5-435a-b628-10cf8193a0b1", - "join_phrase": "" - } - ], - "caa_id": 31997791614, - "caa_release_mbid": "1969cf4e-e6bc-464b-9dd1-ae99e5e14bf6", - "listen_count": 2, - "release_group_mbid": "37d2a507-dd56-41d4-925a-0c2c160be711", - "release_group_name": "Waalian" - }, - { - "artist_mbids": [ - "bda9891c-f6a4-442b-8f1c-65429c3347d8" - ], - "artist_name": "King", - "artists": [ - { - "artist_credit_name": "King", - "artist_mbid": "bda9891c-f6a4-442b-8f1c-65429c3347d8", - "join_phrase": "" - } - ], - "caa_id": 34624858128, - "caa_release_mbid": "18f3b5a6-6097-49a5-87ee-173532dbe1a1", - "listen_count": 2, - "release_group_mbid": "3c0546ed-b263-4f1b-931b-8d75c1bc5b2a", - "release_group_name": "The Carnival" - }, - { - "artist_mbids": [ - "ffabe84e-ec03-4e28-8671-1847224ab074" - ], - "artist_name": "Josh Brar", - "artists": [ - { - "artist_credit_name": "Josh Brar", - "artist_mbid": "ffabe84e-ec03-4e28-8671-1847224ab074", - "join_phrase": "" - } - ], - "caa_id": 40714612761, - "caa_release_mbid": "12acbd24-b33f-4c46-8798-6b32a740be3b", - "listen_count": 2, - "release_group_mbid": "b3b91eca-7890-4644-858d-4392060978e4", - "release_group_name": "Tere Bina Na Guzara E" - }, - { - "artist_mbids": [ - "5d77d4cf-febf-4e5c-b8e7-16ea5ffb0439" - ], - "artist_name": "Aditya Rikhari", - "artists": [ - { - "artist_credit_name": "Aditya Rikhari", - "artist_mbid": "5d77d4cf-febf-4e5c-b8e7-16ea5ffb0439", - "join_phrase": "" - } - ], - "caa_id": 35225071094, - "caa_release_mbid": "dd67ee08-49ef-4508-bf86-8c3eaf69791b", - "listen_count": 2, - "release_group_mbid": "447d85e7-2b6a-4974-9ecb-21f18678dc8c", - "release_group_name": "Samjho Na" - }, - { - "artist_mbids": [], - "artist_name": "Majid Unpeek, Lola", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 2, - "release_group_mbid": None, - "release_group_name": "Nion Nihon Nights" - }, - { - "artist_mbids": [ - "addca31a-40ae-4152-afb8-a477da5191f0" - ], - "artist_name": "Anuv Jain", - "artists": [ - { - "artist_credit_name": "Anuv Jain", - "artist_mbid": "addca31a-40ae-4152-afb8-a477da5191f0", - "join_phrase": "" - } - ], - "caa_id": 33591219607, - "caa_release_mbid": "1e3e4f14-4139-44b5-a08c-c5beee6e8409", - "listen_count": 2, - "release_group_mbid": "6d04c3a2-0080-4aae-8069-95f0738958fb", - "release_group_name": "Meri Baaton Mein Tu" - }, - { - "artist_mbids": [ - "985a6779-0e8a-4e52-9aca-597e2e65ab13", - "32560f6d-f87f-4475-a6fb-7c302036aaf1", - "50fd3c55-9c32-490c-bfd5-679208483901", - "27a58074-387a-4f9d-ab4f-1655158cb10f", - "93622908-0806-4173-94c1-9e42597af011" - ], - "artist_name": "Darshan Raval, Jonita Gandhi, Rochak Kohli, Gurpreet Saini & Sonu Nigam", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 2, - "release_group_mbid": None, - "release_group_name": "Ishq Vishk Rebound (Original Motion Picture Soundtrack)" - }, - { - "artist_mbids": [ - "f931c961-b647-4861-be8c-f47d84a4de51" - ], - "artist_name": "Diljit Dosanjh", - "artists": [ - { - "artist_credit_name": "Diljit Dosanjh", - "artist_mbid": "f931c961-b647-4861-be8c-f47d84a4de51", - "join_phrase": "" - } - ], - "caa_id": 37129944334, - "caa_release_mbid": "962f6b5a-d6ca-4c02-b47c-b3752b1f90c5", - "listen_count": 2, - "release_group_mbid": "3920b520-1404-495b-8557-5b2091233a14", - "release_group_name": "Ghost" - }, - { - "artist_mbids": [], - "artist_name": "Darshan Raval", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 2, - "release_group_mbid": None, - "release_group_name": "Ek Tarfa" - }, - { - "artist_mbids": [], - "artist_name": "Pritam, Arijit Singh, Irshad Kamil", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 2, - "release_group_mbid": None, - "release_group_name": "Dunki" - }, - { - "artist_mbids": [ - "f931c961-b647-4861-be8c-f47d84a4de51" - ], - "artist_name": "Diljit Dosanjh", - "artists": [ - { - "artist_credit_name": "Diljit Dosanjh", - "artist_mbid": "f931c961-b647-4861-be8c-f47d84a4de51", - "join_phrase": "" - } - ], - "caa_id": 33030341692, - "caa_release_mbid": "f9fc0be7-8b5f-4382-bc2a-182938c8a765", - "listen_count": 2, - "release_group_mbid": "23279dea-32b0-47a5-bc4a-68924262c5ec", - "release_group_name": "Drive Thru" - }, - { - "artist_mbids": [ - "ed3f4831-e3e0-4dc0-9381-f5649e9df221" - ], - "artist_name": "Arijit Singh", - "artists": [ - { - "artist_credit_name": "Arijit Singh", - "artist_mbid": "ed3f4831-e3e0-4dc0-9381-f5649e9df221", - "join_phrase": "" - } - ], - "caa_id": 40653893324, - "caa_release_mbid": "3e063d04-3ce6-42f3-af59-2725af2d4c9d", - "listen_count": 2, - "release_group_mbid": "a4bd61ea-eadb-4f68-b552-482490462b2a", - "release_group_name": "Arijit\u2019s Aura" - }, - { - "artist_mbids": [], - "artist_name": "LVTA", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 1, - "release_group_mbid": None, - "release_group_name": "wicked" - }, - { - "artist_mbids": [], - "artist_name": "yellofish", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 1, - "release_group_mbid": None, - "release_group_name": "what\u00b4s my name" - }, - { - "artist_mbids": [], - "artist_name": "LVTA", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 1, - "release_group_mbid": None, - "release_group_name": "until we meet again" - }, - { - "artist_mbids": [], - "artist_name": "Lunar Lullaby & yorukaze", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 1, - "release_group_mbid": None, - "release_group_name": "traffic" - }, - { - "artist_mbids": [], - "artist_name": "chanchito estafa", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 1, - "release_group_mbid": None, - "release_group_name": "the house cat" - }, - { - "artist_mbids": [], - "artist_name": "Kaptain Kola", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 1, - "release_group_mbid": None, - "release_group_name": "take it back" - }, - { - "artist_mbids": [], - "artist_name": "Lunar Lullaby & yorukaze", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 1, - "release_group_mbid": None, - "release_group_name": "sweet" - }, - { - "artist_mbids": [], - "artist_name": "slowburn", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 1, - "release_group_mbid": None, - "release_group_name": "still images" - }, - { - "artist_mbids": [], - "artist_name": "LVTA", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 1, - "release_group_mbid": None, - "release_group_name": "silver sky" - }, - { - "artist_mbids": [], - "artist_name": "J0pie", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 1, - "release_group_mbid": None, - "release_group_name": "signs of winter" - }, - { - "artist_mbids": [], - "artist_name": "Bcalm, Dryden", - "artists": None, - "caa_id": None, - "caa_release_mbid": None, - "listen_count": 1, - "release_group_mbid": None, - "release_group_name": "seasons" - } - ], - "to_ts": 1741564800, - "total_release_group_count": 194, - "user_id": "holycow23" - } -} - release_groups_list = temp_data["payload"]["release_groups"] + user, stats_range = _validate_stats_user_params(user_name) + offset = get_non_negative_param("offset", default=0) + count = get_non_negative_param("count", default=DEFAULT_ITEMS_PER_GET) + stats = db_stats.get(user["id"], "release_groups", stats_range, EntityRecord) + if stats is None: + raise APINoContent('') + + release_groups_list, _ = _process_user_entity(stats, offset, count, entire_range=True) result = _get_artist_activity(release_groups_list) return jsonify({"result": result}) From bf1d9a7e519acd781ce5f95cc469ea9ff39a9419 Mon Sep 17 00:00:00 2001 From: Granth Bagadia <66285223+granth23@users.noreply.github.com> Date: Tue, 18 Mar 2025 23:27:08 +0530 Subject: [PATCH 14/21] Update frontend/js/src/user/stats/components/UserArtistActivity.tsx Co-authored-by: Ansh Goyal --- frontend/js/src/user/stats/components/UserArtistActivity.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index 169485cd41..8a66fa20fe 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -179,7 +179,7 @@ export default function UserArtistActivity(props: UserArtistActivityProps) { onClick={(barData, event) => { const albumName = barData.id; const artistName = barData.indexValue; - const releaseMbid = + const releaseGroupMbid = albumRedirectMapping[`${artistName}-${albumName}`]; if (releaseMbid) { window.location.href = `/album/${releaseMbid}`; From e4749ac073c94c16736ab52121b711d9cd5d0112 Mon Sep 17 00:00:00 2001 From: Granth Bagadia <66285223+granth23@users.noreply.github.com> Date: Tue, 18 Mar 2025 23:28:00 +0530 Subject: [PATCH 15/21] Update UserArtistActivity.tsx --- frontend/js/src/user/stats/components/UserArtistActivity.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index 8a66fa20fe..25c03eb783 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -182,7 +182,7 @@ export default function UserArtistActivity(props: UserArtistActivityProps) { const releaseGroupMbid = albumRedirectMapping[`${artistName}-${albumName}`]; if (releaseMbid) { - window.location.href = `/album/${releaseMbid}`; + navigate(`/album/${releaseMbid}`); } }} /> From 03fffdbcf0624873e5a7c1045613dd1ddeb427db Mon Sep 17 00:00:00 2001 From: Granth Bagadia <66285223+granth23@users.noreply.github.com> Date: Tue, 18 Mar 2025 23:32:02 +0530 Subject: [PATCH 16/21] Update UserArtistActivity.tsx --- frontend/js/src/user/stats/components/UserArtistActivity.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index 25c03eb783..e5a675db29 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -182,7 +182,7 @@ export default function UserArtistActivity(props: UserArtistActivityProps) { const releaseGroupMbid = albumRedirectMapping[`${artistName}-${albumName}`]; if (releaseMbid) { - navigate(`/album/${releaseMbid}`); + navigate(`/album/${releaseGroupMbid}`); } }} /> From dddc1cc74c90fa1702b360beb07aaedeb6d8c2e2 Mon Sep 17 00:00:00 2001 From: Granth Bagadia <66285223+granth23@users.noreply.github.com> Date: Tue, 18 Mar 2025 23:36:55 +0530 Subject: [PATCH 17/21] Update UserArtistActivity.tsx --- frontend/js/src/user/stats/components/UserArtistActivity.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index e5a675db29..e1096e4549 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -7,6 +7,7 @@ import { useQuery } from "@tanstack/react-query"; import Card from "../../../components/Card"; import Loader from "../../../components/Loader"; import { COLOR_BLACK } from "../../../utils/constants"; +import useNavigate from "react-router-dom"; import GlobalAppContext from "../../../utils/GlobalAppContext"; export type UserArtistActivityProps = { @@ -21,6 +22,7 @@ export declare type ChartDataItem = { export default function UserArtistActivity(props: UserArtistActivityProps) { const { APIService } = React.useContext(GlobalAppContext); + const navigate = useNavigate(); // Props const { user, range } = props; From 5829bcf32b28878b2f7944d4cd8283fc1a6d5d2e Mon Sep 17 00:00:00 2001 From: Granth Bagadia <66285223+granth23@users.noreply.github.com> Date: Tue, 18 Mar 2025 23:43:59 +0530 Subject: [PATCH 18/21] Update UserArtistActivity.tsx --- frontend/js/src/user/stats/components/UserArtistActivity.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index e1096e4549..4df8bb4062 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -183,7 +183,7 @@ export default function UserArtistActivity(props: UserArtistActivityProps) { const artistName = barData.indexValue; const releaseGroupMbid = albumRedirectMapping[`${artistName}-${albumName}`]; - if (releaseMbid) { + if (releaseGroupMbid) { navigate(`/album/${releaseGroupMbid}`); } }} From 53f80ca77d99eab58f7019ed5356eb717ab3ba65 Mon Sep 17 00:00:00 2001 From: Granth Bagadia <66285223+granth23@users.noreply.github.com> Date: Tue, 18 Mar 2025 23:56:13 +0530 Subject: [PATCH 19/21] Update UserArtistActivity.tsx --- frontend/js/src/user/stats/components/UserArtistActivity.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index 4df8bb4062..23d5522df3 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -7,7 +7,7 @@ import { useQuery } from "@tanstack/react-query"; import Card from "../../../components/Card"; import Loader from "../../../components/Loader"; import { COLOR_BLACK } from "../../../utils/constants"; -import useNavigate from "react-router-dom"; +import { useNavigate } from "react-router-dom"; import GlobalAppContext from "../../../utils/GlobalAppContext"; export type UserArtistActivityProps = { From e10574e3610a4786e7ca42fa454b75e71d48d1c2 Mon Sep 17 00:00:00 2001 From: Granth Bagadia <66285223+granth23@users.noreply.github.com> Date: Wed, 19 Mar 2025 00:00:13 +0530 Subject: [PATCH 20/21] Update UserArtistActivity.tsx --- frontend/js/src/user/stats/components/UserArtistActivity.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/js/src/user/stats/components/UserArtistActivity.tsx b/frontend/js/src/user/stats/components/UserArtistActivity.tsx index 23d5522df3..7b4f709b1e 100644 --- a/frontend/js/src/user/stats/components/UserArtistActivity.tsx +++ b/frontend/js/src/user/stats/components/UserArtistActivity.tsx @@ -4,10 +4,10 @@ import { faExclamationCircle, faLink } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { useQuery } from "@tanstack/react-query"; +import { useNavigate } from "react-router-dom"; import Card from "../../../components/Card"; import Loader from "../../../components/Loader"; import { COLOR_BLACK } from "../../../utils/constants"; -import { useNavigate } from "react-router-dom"; import GlobalAppContext from "../../../utils/GlobalAppContext"; export type UserArtistActivityProps = { From 3b334ae15cee5e681dcba77be3cfa0589a33a329 Mon Sep 17 00:00:00 2001 From: anshg1214 Date: Thu, 20 Mar 2025 21:04:49 +0000 Subject: [PATCH 21/21] feat: Optimize artist activity sorting using heapq --- listenbrainz/webserver/views/stats_api.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/listenbrainz/webserver/views/stats_api.py b/listenbrainz/webserver/views/stats_api.py index 9a42b5017b..843c9976b5 100644 --- a/listenbrainz/webserver/views/stats_api.py +++ b/listenbrainz/webserver/views/stats_api.py @@ -9,6 +9,7 @@ import listenbrainz.db.user as db_user import pycountry import requests +import heapq from data.model.common_stat import StatApi, StatisticsRange, StatRecordList from data.model.user_artist_map import UserArtistMapRecord, UserArtistMapArtist @@ -434,10 +435,7 @@ def _get_artist_activity(release_groups_list): artist_data["name"] = artist_name artist_data["albums"] = list(artist_data["albums"].values()) - sorted_data = sorted(result.values(), key=lambda x: x["listen_count"], reverse=True) - count = 15 - N = min(count, len(sorted_data)) - return sorted_data[:N] + return heapq.nlargest(15, result.values(), key=lambda x: x["listen_count"]) @stats_api_bp.get("/user//artist-activity") @crossdomain