Skip to content
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

LB-1264: Releases by "Various Artists" should not be included on "Fresh releases" page #2688

Open
wants to merge 9 commits into
base: master
Choose a base branch
from
126 changes: 86 additions & 40 deletions frontend/js/src/explore/fresh-releases/FreshReleases.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { uniqBy } from "lodash";
import Spinner from "react-loader-spinner";
import { toast } from "react-toastify";
import { Helmet } from "react-helmet";
import { useQuery } from "@tanstack/react-query";
import GlobalAppContext from "../../utils/GlobalAppContext";
import { ToastMsg } from "../../notifications/Notifications";
import ReleaseFilters from "./components/ReleaseFilters";
Expand Down Expand Up @@ -96,23 +97,20 @@ export const PAGE_TYPE_SITEWIDE: string = "sitewide";

export type filterRangeOption = keyof typeof filterRangeOptions;

type FreshReleasesData = {
releases: Array<FreshReleaseItem>;
releaseTypes: Array<string>;
releaseTags: Array<string>;
};

export default function FreshReleases() {
const { APIService, currentUser } = React.useContext(GlobalAppContext);

const isLoggedIn: boolean = Object.keys(currentUser).length !== 0;

const [releases, setReleases] = React.useState<Array<FreshReleaseItem>>([]);
const [filteredList, setFilteredList] = React.useState<
Array<FreshReleaseItem>
>([]);
const [allFilters, setAllFilters] = React.useState<{
releaseTypes: Array<string | undefined>;
releaseTags: Array<string | undefined>;
}>({
releaseTypes: [],
releaseTags: [],
});
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const [pageType, setPageType] = React.useState<string>(
isLoggedIn ? PAGE_TYPE_USER : PAGE_TYPE_SITEWIDE
);
Expand Down Expand Up @@ -158,11 +156,28 @@ export default function FreshReleases() {
pillRowStyle = { justifyContent: "flex-end" };
}

React.useEffect(() => {
const fetchReleases = async () => {
setIsLoading(true);
let freshReleases: Array<FreshReleaseItem>;
const queryKey =
pageType === PAGE_TYPE_SITEWIDE
? [
"fresh-release-sitewide",
filterRangeOptions[range].value,
showPastReleases,
showFutureReleases,
sort,
]
: [
"fresh-release-user",
currentUser.name,
showPastReleases,
showFutureReleases,
sort,
];

const { data: loaderData, isLoading } = useQuery({
queryKey,
queryFn: async () => {
try {
let freshReleases: Array<FreshReleaseItem>;
if (pageType === PAGE_TYPE_SITEWIDE) {
const allFreshReleases = await APIService.fetchSitewideFreshReleases(
filterRangeOptions[range].value,
Expand All @@ -180,7 +195,6 @@ export default function FreshReleases() {
);
freshReleases = userFreshReleases.payload.releases;
}

const cleanReleases = uniqBy(freshReleases, (datum) => {
return (
/*
Expand Down Expand Up @@ -217,29 +231,66 @@ export default function FreshReleases() {
const releaseTags = Array.from(uniqueReleaseTagsSet);
releaseTags.sort();

setReleases(cleanReleases);
setFilteredList(cleanReleases);
setAllFilters({
releaseTypes,
releaseTags,
});
setIsLoading(false);
return {
data: {
releases: cleanReleases,
releaseTypes,
releaseTags,
} as FreshReleasesData,
hasError: false,
errorMessage: "",
};
} catch (error) {
toast.error(
<ToastMsg
title="Couldn't fetch fresh releases"
message={
typeof error === "object" ? error.message : error.toString()
}
message={error.message}
/>,
{ toastId: "fetch-error" }
);
return {
data: {
releases: [],
releaseTypes: [],
releaseTags: [],
} as FreshReleasesData,
hasError: true,
errorMessage: error.message,
};
}
};
// Call the async function defined above (useEffect can't return a Promise)
fetchReleases();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [pageType, range, showPastReleases, showFutureReleases, sort]);
},
});

const {
data: rawData = {
releases: [],
releaseTypes: [],
releaseTags: [],
} as FreshReleasesData,
hasError = false,
errorMessage = "",
} = loaderData || {};

const { releases, releaseTypes, releaseTags } = rawData;

const releasesString = JSON.stringify(releases);
React.useEffect(() => {
const newReleases = JSON.parse(releasesString);
setFilteredList(newReleases);
}, [releasesString]);
Comment on lines +276 to +280
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure how to test this locally properly, but I wonder if you need the stringification here.
Does this result int he same re-rendering loop issue still?
If so, I think something smells a bit fishy with the way we manipulate the state between parent and child components...

React.useEffect(() => {
    setFilteredList(releases);
  }, [releases]);


let alt;
let message;
if (hasError) {
alt = "Error fetching releases";
message = `Error fetching releases: ${errorMessage}`;
} else if (releases.length === 0) {
alt = "No releases";
message = "No releases";
} else {
alt = "No filtered releases";
message = `0/${releases.length} releases match your filters.`;
}

return (
<>
Expand All @@ -253,6 +304,7 @@ export default function FreshReleases() {
<div className="fresh-releases-row">
<Pill
id="sitewide-releases"
data-testid="sitewide-releases-pill"
onClick={() => {
setPageType(PAGE_TYPE_SITEWIDE);
setSort("release_date");
Expand Down Expand Up @@ -337,17 +389,9 @@ export default function FreshReleases() {
<div className="no-release">
<img
src="/static/img/recommendations/no-freshness.png"
alt={
releases.length === 0
? "No releases"
: "No filtered releases"
}
alt={alt}
/>
<div className="text-muted">
{releases.length === 0
? "No releases"
: `0/${releases.length} releases match your filters.`}
</div>
<div className="text-muted">{message}</div>
</div>
) : (
<ReleaseCardsGrid
Expand All @@ -368,8 +412,10 @@ export default function FreshReleases() {
/>
)}
<ReleaseFilters
allFilters={allFilters}
releases={releases}
releaseTypes={releaseTypes}
releaseTags={releaseTags}
filteredList={filteredList}
setFilteredList={setFilteredList}
range={range}
handleRangeChange={setRange}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,8 +138,8 @@ export default function ReleaseCard(props: ReleaseCardProps) {
}, [releaseMBID, releaseGroupMBID, caaID, caaReleaseMBID, setCoverartSrc]);

const linkToEntity = releaseGroupMBID
? `/album/${releaseGroupMBID}`
: `/release/${releaseMBID}`;
? `/album/${releaseGroupMBID}/`
: `/release/${releaseMBID}/`;
return (
<div className="release-card-container">
<div className="release-item">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import * as React from "react";
import { faChevronDown, faChevronUp } from "@fortawesome/free-solid-svg-icons";
import { faCircleXmark } from "@fortawesome/free-regular-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import * as _ from "lodash";
import Switch from "../../../components/Switch";
import SideBar from "../../../components/Sidebar";
import type {
Expand All @@ -11,12 +12,13 @@ import type {
} from "../FreshReleases";
import { PAGE_TYPE_SITEWIDE, filterRangeOptions } from "../FreshReleases";

const VARIOUS_ARTISTS_MBID = "89ad4ac3-39f7-470e-963a-56509c546377";

type ReleaseFiltersProps = {
allFilters: {
releaseTypes: Array<string | undefined>;
releaseTags: Array<string | undefined>;
};
releases: Array<FreshReleaseItem>;
releaseTypes: Array<string>;
releaseTags: Array<string>;
filteredList: Array<FreshReleaseItem>;
setFilteredList: React.Dispatch<
React.SetStateAction<Array<FreshReleaseItem>>
>;
Expand All @@ -34,8 +36,10 @@ type ReleaseFiltersProps = {

export default function ReleaseFilters(props: ReleaseFiltersProps) {
const {
allFilters,
releaseTypes,
releaseTags,
releases,
filteredList,
setFilteredList,
range,
handleRangeChange,
Expand All @@ -55,6 +59,9 @@ export default function ReleaseFilters(props: ReleaseFiltersProps) {
const [releaseTagsCheckList, setReleaseTagsCheckList] = React.useState<
Array<string | undefined>
>([]);
const [includeVariousArtists, setIncludeVariousArtists] = React.useState<
boolean
>(false);

const [
releaseTagsExcludeCheckList,
Expand Down Expand Up @@ -141,10 +148,18 @@ export default function ReleaseFilters(props: ReleaseFiltersProps) {
)
: Object.values(filterRangeOptions);

// Reset filters when range changes
React.useEffect(() => {
setCoverartOnly(false);
setCheckedList([]);
}, [allFilters]);
if (coverartOnly === true) {
setCoverartOnly(false);
}
if (checkedList?.length > 0) {
setCheckedList([]);
}
if (includeVariousArtists === true) {
setIncludeVariousArtists(false);
}
}, [releaseTags, releaseTypes]);

React.useEffect(() => {
const filteredReleases = releases.filter((item) => {
Expand All @@ -162,29 +177,42 @@ export default function ReleaseFilters(props: ReleaseFiltersProps) {
releaseTagsExcludeCheckList.includes(tag)
);

const isVariousArtists = item.artist_mbids.some(
(mbid) => mbid === VARIOUS_ARTISTS_MBID
);
const isVariousArtistsValid = !isVariousArtists || includeVariousArtists;

return (
isCoverArtValid &&
isReleaseTypeValid &&
isReleaseTagValid &&
!isReleaseTagExcluded
!isReleaseTagExcluded &&
isVariousArtistsValid
);
});

setFilteredList(filteredReleases);
if (!_.isEqual(filteredReleases, filteredList)) {
setFilteredList(filteredReleases);
}
if (releaseCardGridRef.current) {
window.scrollTo(0, releaseCardGridRef.current.offsetTop);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
checkedList,
releaseTagsCheckList,
releaseTagsExcludeCheckList,
coverartOnly,
includeVariousArtists,
releases,
]);

return (
<SideBar>
<div className="sidebar-header">
<div
className="sidebar-header"
data-testid="sidebar-header-fresh-releases"
>
<p>Fresh Releases</p>
<p>Listen to recent releases, and browse what&apos;s dropping soon.</p>
<p>
Expand Down Expand Up @@ -246,10 +274,10 @@ export default function ReleaseFilters(props: ReleaseFiltersProps) {
onChange={(e) => setShowFutureReleases(!showFutureReleases)}
switchLabel="Future"
/>
{allFilters.releaseTypes.length > 0 && (
{releaseTypes.length > 0 && (
<>
<span id="types">Types:</span>
{allFilters.releaseTypes?.map((type, index) => (
{releaseTypes?.map((type, index) => (
<Switch
id={`filters-item-${index}`}
key={`filters-item-${type}`}
Expand All @@ -262,7 +290,7 @@ export default function ReleaseFilters(props: ReleaseFiltersProps) {
</>
)}

{allFilters.releaseTags.length > 0 && (
{releaseTags.length > 0 && (
<>
<span id="tags">Include (only):</span>
<select
Expand All @@ -274,7 +302,7 @@ export default function ReleaseFilters(props: ReleaseFiltersProps) {
<option value="" disabled>
selection genre/tag...
</option>
{allFilters.releaseTags
{releaseTags
?.filter((tag) => !releaseTagsCheckList.includes(tag))
?.map((tag, index) => (
<option value={tag} key={tag}>
Expand Down Expand Up @@ -305,7 +333,7 @@ export default function ReleaseFilters(props: ReleaseFiltersProps) {
<option value="" disabled>
selection genre/tag...
</option>
{allFilters.releaseTags
{releaseTags
?.filter(
(tag) => !releaseTagsExcludeCheckList.includes(tag)
)
Expand Down Expand Up @@ -364,8 +392,17 @@ export default function ReleaseFilters(props: ReleaseFiltersProps) {
switchLabel="Only Releases with artwork"
/>

<Switch
id="include-various-artists-switch"
key="include-various-artists-switch"
value="various-artists"
checked={includeVariousArtists}
onChange={(e) => setIncludeVariousArtists(!includeVariousArtists)}
switchLabel="Releases by Various Artists"
/>

{Object.keys(displaySettings).map((setting, index) =>
(setting === "Tags" && allFilters.releaseTags.length === 0) ||
(setting === "Tags" && releaseTags.length === 0) ||
(setting === "Listens" && !totalListenCount) ? null : (
<Switch
id={`display-item-${index}`}
Expand Down