-
-
Notifications
You must be signed in to change notification settings - Fork 249
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
anshg1214
merged 26 commits into
metabrainz:master
from
granth23:LB-1737-User-Artist-Map
Mar 20, 2025
Merged
Changes from 16 commits
Commits
Show all changes
26 commits
Select commit
Hold shift + click to select a range
11ed834
new stats added for user
granth23 d0ca023
nivo rocks chart bug fix
granth23 bedff45
Merge branch 'metabrainz:master' into LB-1737-User-Artist-Map
granth23 c32aecb
db returns fixed
granth23 38d481e
bugs fixed amd total artist limit added
granth23 7721189
sitewide activity added
granth23 f49def5
test component added
granth23 472bc57
test added
granth23 e6108a9
album redirect added
granth23 d7b6c14
all artists names included
granth23 79e5785
text-wrap and better optimization
granth23 0ca50ff
Merge branch 'metabrainz:master' into LB-1737-User-Artist-Map
granth23 eee1df3
key-index bug fix
granth23 d947652
Rotation added to artist names
granth23 1991813
minor bug fix
granth23 fb406dc
Merge branch 'metabrainz:master' into LB-1737-User-Artist-Map
granth23 bf1d9a7
Update frontend/js/src/user/stats/components/UserArtistActivity.tsx
granth23 e4749ac
Update UserArtistActivity.tsx
granth23 03fffdb
Update UserArtistActivity.tsx
granth23 dddc1cc
Update UserArtistActivity.tsx
granth23 5829bcf
Update UserArtistActivity.tsx
granth23 53f80ca
Update UserArtistActivity.tsx
granth23 e10574e
Update UserArtistActivity.tsx
granth23 e26a8e8
Merge branch 'metabrainz:master' into LB-1737-User-Artist-Map
granth23 8c16135
Merge branch 'master' into LB-1737-User-Artist-Map
anshg1214 3b334ae
feat: Optimize artist activity sorting using heapq
anshg1214 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
196 changes: 196 additions & 0 deletions
196
frontend/js/src/user/stats/components/UserArtistActivity.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
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 declare type ChartDataItem = { | ||
label: string; | ||
[albumName: string]: number | string; | ||
}; | ||
|
||
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], | ||
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) => { | ||
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<string, number> | ||
), | ||
}; | ||
}) as ChartDataItem[]; | ||
}; | ||
const [chartData, setChartData] = React.useState<ChartDataItem[]>([]); | ||
|
||
const albumRedirectMapping = React.useMemo(() => { | ||
const mapping: Record<string, string> = {}; | ||
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); | ||
setChartData(processedData); | ||
} | ||
}, [rawData]); | ||
|
||
return ( | ||
<Card className="user-stats-card" data-testid="user-artist-activity"> | ||
<div className="row"> | ||
<div className="col-xs-10"> | ||
<h3 className="capitalize-bold" style={{ marginLeft: 20 }}> | ||
Artist Activity | ||
</h3> | ||
</div> | ||
<div className="col-xs-2 text-right"> | ||
<h4 style={{ marginTop: 20 }}> | ||
<a href="#artist-activity"> | ||
<FontAwesomeIcon | ||
icon={faLink as IconProp} | ||
size="sm" | ||
color={COLOR_BLACK} | ||
style={{ marginRight: 20 }} | ||
/> | ||
</a> | ||
</h4> | ||
</div> | ||
</div> | ||
<Loader isLoading={loading}> | ||
{hasError ? ( | ||
<div | ||
style={{ | ||
display: "flex", | ||
alignItems: "center", | ||
justifyContent: "center", | ||
minHeight: "inherit", | ||
}} | ||
> | ||
<span style={{ fontSize: 24 }}> | ||
<FontAwesomeIcon icon={faExclamationCircle as IconProp} />{" "} | ||
{errorMessage} | ||
</span> | ||
</div> | ||
) : ( | ||
<div className="row"> | ||
<div className="col-xs-12"> | ||
<div | ||
style={{ width: "100%", height: "600px", minHeight: "400px" }} | ||
> | ||
<ResponsiveBar | ||
data={chartData} | ||
keys={Array.from( | ||
new Set( | ||
chartData.flatMap((item) => | ||
Object.keys(item).filter((key) => key !== "label") | ||
) | ||
) | ||
)} | ||
indexBy="label" | ||
margin={{ top: 20, right: 80, bottom: 80, left: 80 }} | ||
padding={0.2} | ||
layout="vertical" | ||
colors={{ scheme: "nivo" }} | ||
borderColor={{ from: "color", modifiers: [["darker", 1.6]] }} | ||
enableLabel={false} | ||
axisBottom={{ | ||
tickSize: 5, | ||
tickPadding: 5, | ||
tickRotation: -45, | ||
renderTick: (tick) => ( | ||
<g transform={`translate(${tick.x},${tick.y})`}> | ||
{tick.value | ||
.split("\n") | ||
.map((line: string, i: number) => ( | ||
<text | ||
key={line} | ||
x={0} | ||
y={10 + i * 15} | ||
textAnchor="end" | ||
dominantBaseline="middle" | ||
style={{ | ||
fontSize: 10, | ||
fill: "#000", | ||
transform: `rotate(-45deg)`, | ||
}} | ||
> | ||
{line} | ||
</text> | ||
))} | ||
</g> | ||
), | ||
}} | ||
onClick={(barData, event) => { | ||
const albumName = barData.id; | ||
const artistName = barData.indexValue; | ||
const releaseMbid = | ||
granth23 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
albumRedirectMapping[`${artistName}-${albumName}`]; | ||
if (releaseMbid) { | ||
window.location.href = `/album/${releaseMbid}`; | ||
} | ||
granth23 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
}} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
)} | ||
</Loader> | ||
</Card> | ||
); | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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" } | ||
] | ||
} | ||
] | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.