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

Refactor homepage recent projects to use ERAS #6397

Merged
merged 3 commits into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,32 +1,30 @@
import { Anchor, Box, ResponsiveContext, Text } from 'grommet'
import { arrayOf, bool, shape, string } from 'prop-types'
import { arrayOf, bool, number, shape, string } from 'prop-types'
import { useContext } from 'react'
import { Loader, ProjectCard, SpacedText } from '@zooniverse/react-components'

import { ContentBox } from '@components/shared'

export default function RecentProjects({
isLoading = false,
projectPreferences = [],
recentProjects = [],
error = undefined
}) {
const size = useContext(ResponsiveContext)

return (
<ContentBox title='Continue Classifying' screenSize={size}>
{isLoading && (
{isLoading ? (
<Box fill justify='center' align='center'>
<Loader />
</Box>
)}
{!isLoading && error && (
) : error ? (
<Box fill justify='center' align='center' pad='medium'>
<SpacedText>
There was an error fetching your recent projects
</SpacedText>
</Box>
)}
{!isLoading && !projectPreferences.length && !error && (
) : !recentProjects.length ? (
<Box fill justify='center' align='center' pad='medium'>
<SpacedText>No Recent Projects found</SpacedText>
<Text>
Expand All @@ -37,41 +35,44 @@ export default function RecentProjects({
.
</Text>
</Box>
) : (
<Box
as='ul'
direction='row'
gap='small'
pad={{ horizontal: 'xxsmall', bottom: 'xsmall', top: 'xxsmall' }}
overflow={{ horizontal: 'auto' }}
style={{ listStyle: 'none' }}
margin='0'
>
{recentProjects.map(project => (
<li key={project.id}>
<ProjectCard
badge={project.count}
description={project?.description}
displayName={project?.display_name}
href={`https://www.zooniverse.org/projects/${project?.slug}`}
imageSrc={project?.avatar_src}
size={size}
/>
</li>
))}
</Box>
)}
{!isLoading &&
projectPreferences?.length ? (
<Box
as='ul'
direction='row'
gap='small'
pad={{ horizontal: 'xxsmall', bottom: 'xsmall', top: 'xxsmall' }}
overflow={{ horizontal: 'auto' }}
style={{ listStyle: 'none' }}
margin='0'
>
{projectPreferences.map(preference => (
<li key={preference?.project?.id}>
<ProjectCard
badge={preference?.activity_count}
description={preference?.project?.description}
displayName={preference?.project?.display_name}
href={`https://www.zooniverse.org/projects/${preference?.project?.slug}`}
imageSrc={preference?.project?.avatar_src}
size={size}
/>
</li>
))}
</Box>
) : null}
</ContentBox>
)
}

RecentProjects.propTypes = {
isLoading: bool,
projectPreferences: arrayOf(
recentProjects: arrayOf(
shape({
id: string
avatar_src: string,
count: number,
description: string,
display_name: string,
id: string,
slug: string
})
)
}
Original file line number Diff line number Diff line change
@@ -1,90 +1,56 @@
import { shape, string } from 'prop-types'
import { panoptes } from '@zooniverse/panoptes-js'
import useSWR from 'swr'
import auth from 'panoptes-client/lib/auth'

import { usePanoptesProjects } from '@hooks'
import { usePanoptesProjects, useStats } from '@hooks'
import RecentProjects from './RecentProjects.js'

const SWROptions = {
revalidateIfStale: true,
revalidateOnMount: true,
revalidateOnFocus: true,
revalidateOnReconnect: true,
refreshInterval: 0
}

async function fetchUserProjectPreferences() {
const user = await auth.checkCurrent()
const token = await auth.checkBearerToken()
const authorization = `Bearer ${token}`
try {
const query = {
sort: '-updated_at',
user_id: user.id
}
const response = await panoptes.get('/project_preferences', query, { authorization })
if (response.ok) {
const projectPreferencesUserHasClassified =
response.body.project_preferences
.filter(preference => preference.activity_count > 0)
return projectPreferencesUserHasClassified
}
return []
} catch (error) {
console.error(error)
throw error
}
}

function RecentProjectsContainer({ authUser }) {
// Get user's project preference.activity_count for 10 most recently classified projects
const cacheKey = {
name: 'user-project-preferences',
userId: authUser.id
const recentProjectsQuery = {
project_contributions: true,
order_project_contributions_by: 'recents',
period: 'day'
}

const {
data: projectPreferences,
isLoading: preferencesLoading,
error: preferencesError
} = useSWR(cacheKey, fetchUserProjectPreferences, SWROptions)
data: stats,
isLoading: statsLoading,
error: statsError
} = useStats({ sourceId: authUser?.id, query: recentProjectsQuery })

// Get more info about each project and attach it to correct projectPreference object
const recentProjectIds = projectPreferences?.map(
preference => preference.links.project
)
// limit to 20 projects fetched from panoptes
const contributions = stats?.project_contributions.slice(0, 20)
const projectIds = contributions?.map(project => project.project_id)

// Get more info about each project
const {
data: projects,
isLoading: projectsLoading,
error: projectsError
} = usePanoptesProjects({
cards: true,
id: recentProjectIds?.join(',')
id: projectIds?.join(',')
})

// Attach project object to each project preference
let projectPreferencesWithProjectObj
if (projects?.length) {
projectPreferencesWithProjectObj = projectPreferences
.map(preference => {
const matchedProjectObj = projects.find(
project => project.id === preference.links?.project
)

if (matchedProjectObj) {
preference.project = matchedProjectObj
// Attach project info to each contribution stat (see similar behavior in TopProjects)
let recentProjects = []

if (projects?.length && contributions?.length) {
recentProjects = contributions
.map(projectContribution => {
const projectData = projects?.find(
project => project.id === projectContribution.project_id.toString()
)
return {
count: projectContribution.count,
...projectData
}
return preference
})
.filter(preference => preference?.project?.slug)
.slice(0, 10)
.filter(project => project?.id) // exclude private or deleted projects
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it's an issue, but noting if I have 20 projects per line 19, but then private/deleted projects are filtered out here, then there might be less than 20 or an inconsistent number of projects for some users.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Agreed. I think this is fine for the homepage, but we'll need to discuss handling private and/or deleted projects when creating an All Projects page with 20-per-page pagination.

}

return (
<RecentProjects
isLoading={preferencesLoading || projectsLoading}
projectPreferences={projectPreferencesWithProjectObj}
error={preferencesError || projectsError}
error={statsError || projectsError}
isLoading={statsLoading || projectsLoading}
recentProjects={recentProjects}
/>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { SpacedText } from '@zooniverse/react-components'
import { Anchor } from 'grommet'
import { shape, string } from 'prop-types'
import { object, oneOfType, shape, string } from 'prop-types'
import styled from 'styled-components'

const StyledAnchor = styled(Anchor)`
font-family: 'Karla', Arial, sans-serif;
font-family: 'Karla', Arial, sans-serif;
font-size: 1rem;
line-height: normal;
background: none;
Expand Down Expand Up @@ -45,7 +45,7 @@ function ContentLink({

ContentLink.propTypes = {
link: shape({
as: string,
as: oneOfType([object, string]),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixes a prop-types warning following #6379

href: string,
text: string
})
Expand Down