Skip to content

LB-1737: Create top artists graph showing album details #3170

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 26 commits into from
Mar 20, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
11ed834
new stats added for user
granth23 Feb 10, 2025
d0ca023
nivo rocks chart bug fix
granth23 Feb 11, 2025
bedff45
Merge branch 'metabrainz:master' into LB-1737-User-Artist-Map
granth23 Feb 11, 2025
c32aecb
db returns fixed
granth23 Feb 14, 2025
38d481e
bugs fixed amd total artist limit added
granth23 Feb 17, 2025
7721189
sitewide activity added
granth23 Feb 17, 2025
f49def5
test component added
granth23 Feb 17, 2025
472bc57
test added
granth23 Feb 18, 2025
e6108a9
album redirect added
granth23 Feb 23, 2025
d7b6c14
all artists names included
granth23 Mar 12, 2025
79e5785
text-wrap and better optimization
granth23 Mar 13, 2025
0ca50ff
Merge branch 'metabrainz:master' into LB-1737-User-Artist-Map
granth23 Mar 13, 2025
eee1df3
key-index bug fix
granth23 Mar 13, 2025
d947652
Rotation added to artist names
granth23 Mar 13, 2025
1991813
minor bug fix
granth23 Mar 13, 2025
fb406dc
Merge branch 'metabrainz:master' into LB-1737-User-Artist-Map
granth23 Mar 13, 2025
bf1d9a7
Update frontend/js/src/user/stats/components/UserArtistActivity.tsx
granth23 Mar 18, 2025
e4749ac
Update UserArtistActivity.tsx
granth23 Mar 18, 2025
03fffdb
Update UserArtistActivity.tsx
granth23 Mar 18, 2025
dddc1cc
Update UserArtistActivity.tsx
granth23 Mar 18, 2025
5829bcf
Update UserArtistActivity.tsx
granth23 Mar 18, 2025
53f80ca
Update UserArtistActivity.tsx
granth23 Mar 18, 2025
e10574e
Update UserArtistActivity.tsx
granth23 Mar 18, 2025
e26a8e8
Merge branch 'metabrainz:master' into LB-1737-User-Artist-Map
granth23 Mar 18, 2025
8c16135
Merge branch 'master' into LB-1737-User-Artist-Map
anshg1214 Mar 20, 2025
3b334ae
feat: Optimize artist activity sorting using heapq
anshg1214 Mar 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions frontend/js/src/user/stats/UserReports.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,12 +168,10 @@ export default function UserReports() {
<UserDailyActivity range={range} user={user} />
</section>
)}
{user && (
<section id="artist-activity">
{statsExplanationModalButton}
<UserArtistActivity range={range} user={user} />
</section>
)}
<section id="artist-activity">
{statsExplanationModalButton}
<UserArtistActivity range={range} user={user} />
</section>
<section id="artist-origin">
{statsExplanationModalButton}
<UserArtistMap range={range} user={user} />
Expand Down
14 changes: 7 additions & 7 deletions frontend/js/src/user/stats/components/UserArtistActivity.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,26 @@ import GlobalAppContext from "../../../utils/GlobalAppContext";

export type UserArtistActivityProps = {
range: UserStatsAPIRange;
user: ListenBrainzUser;
user?: ListenBrainzUser;
};

export declare type ChartDataItem = {
label: string;
[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: "" };
Expand Down
10 changes: 8 additions & 2 deletions frontend/js/src/utils/APIService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -575,10 +575,16 @@ export default class APIService {
};

getUserArtistActivity = async (
userName: string,
userName?: string,
range: UserStatsAPIRange = "all_time"
): Promise<UserArtistActivityResponse> => {
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) {
Expand Down
94 changes: 91 additions & 3 deletions listenbrainz/webserver/views/stats_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/<user_name>/daily-activity")
Expand Down Expand Up @@ -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}")
Expand All @@ -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,
Expand Down Expand Up @@ -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()
Expand Down