Skip to content

Commit

Permalink
lib-user: Refactor GroupStats with existing MainContent, ContentBox, …
Browse files Browse the repository at this point in the history
…and ProjectCard components (#6014)

* Move MainContent component to components/shared

* Refactor GroupStatsContainer with project and dateRange state, update stats hooks

* Add MainContent to GroupStats

* Add TopContributors placeholder and init TopProjects to GroupStats

* Update imports
  • Loading branch information
mcbouslog authored Apr 5, 2024
1 parent 72a4e32 commit f2bb696
Show file tree
Hide file tree
Showing 11 changed files with 188 additions and 44 deletions.
141 changes: 118 additions & 23 deletions packages/lib-user/src/components/GroupStats/GroupStats.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { Grid } from 'grommet'
import { arrayOf, func, number, shape, string } from 'prop-types'

import {
ContentBox,
Layout,
MainContent,
ProjectCard
} from '@components/shared'

import DeleteGroup from './DeleteGroup'
import EditGroup from './EditGroup'

Expand All @@ -10,6 +18,7 @@ const DEFAULT_GROUP = {
const DEFAULT_HANDLER = () => true
const DEFAULT_STATS = {
active_users: 0,
data: [],
project_contributions: [
{
count: 0,
Expand All @@ -18,54 +27,140 @@ const DEFAULT_STATS = {
}
],
time_spent: 0,
top_contributors: [
{
count: 0,
session_time: 0,
user_id: 0
}
],
total_count: 0
}

function GroupStats({
allProjectsStats = DEFAULT_STATS,
group = DEFAULT_GROUP,
groupStats = DEFAULT_STATS,
handleDateRangeSelect = DEFAULT_HANDLER,
handleGroupDelete = DEFAULT_HANDLER,
handleGroupUpdate = DEFAULT_HANDLER
handleGroupUpdate = DEFAULT_HANDLER,
handleProjectSelect = DEFAULT_HANDLER,
projectStats = DEFAULT_STATS,
projects = [],
selectedDateRange = 'Last7Days',
selectedProject = 'AllProjects'
}) {
// set stats based on selected project or all projects
const stats = selectedProject === 'AllProjects' ? allProjectsStats : projectStats

// set top projects based on selected date range and all project stats
let topProjects = []
const topProjectContributions = allProjectsStats.project_contributions
.sort((a, b) => b.count - a.count)

topProjects = topProjectContributions
.map(projectContribution => {
const projectData = projects?.find(project => project.id === projectContribution.project_id.toString())
return projectData
})
.filter(project => project)
.slice(0, 6)

return (
<div>
<h2>Hi group with ID {group?.id}! 🙌</h2>
<h3>Your group display_name is {group?.display_name}.</h3>
<h4>Members: <pre>{group?.links?.users?.toString()}</pre></h4>
<h4>Here are your group stats:</h4>
<pre>{JSON.stringify(groupStats, null, 2)}</pre>
<hr />
<Layout>
<MainContent
handleDateRangeSelect={handleDateRangeSelect}
handleProjectSelect={handleProjectSelect}
projects={projects}
selectedDateRange={selectedDateRange}
selectedProject={selectedProject}
stats={stats}
user={group}
/>
<Grid
columns='1/2'
gap='30px'
>
<ContentBox
title='Top Contributors'
>
Top contributors go here.
</ContentBox>
<ContentBox
linkLabel='See more'
linkProps={{ href: 'https://www.zooniverse.org/projects' }}
title='Top Projects'
width='625px'
>
<Grid
justify='center'
columns='1/3'
gap='small'
>
{topProjects.map(topProject => {
return (
<ProjectCard
key={topProject?.id}
description={topProject?.description}
displayName={topProject?.display_name}
href={`https://www.zooniverse.org/projects/${topProject?.slug}`}
imageSrc={topProject?.avatar_src}
small
/>
)
})}
</Grid>
</ContentBox>
</Grid>
<EditGroup
group={group}
handleGroupUpdate={handleGroupUpdate}
/>
<br />
<hr />
<br />
<DeleteGroup
handleGroupDelete={handleGroupDelete}
/>
</div>
</Layout>
)
}

const statsShape = shape({
active_users: number,
data: arrayOf(shape({
count: number,
period: string,
session_time: number
})),
project_contributions: arrayOf(shape({
count: number,
project_id: number,
session_time: number
})),
time_spent: number,
top_contributors: arrayOf(shape({
count: number,
session_time: number,
user_id: number
})),
total_count: number
})

GroupStats.propTypes = {
allProjectsStats: statsShape,
group: shape({
display_name: string,
id: string
}),
groupStats: shape({
active_users: number,
project_contributions: arrayOf(shape({
count: number,
project_id: number,
session_time: number
})),
time_spent: number,
total_count: number
}),
handleDateRangeSelect: func,
handleGroupDelete: func,
handleGroupUpdate: func
handleGroupUpdate: func,
handleProjectSelect: func,
projectStats: statsShape,
projects: arrayOf(shape({
id: string,
display_name: string
})),
selectedDateRange: string,
selectedProject: string
}

export default GroupStats
68 changes: 59 additions & 9 deletions packages/lib-user/src/components/GroupStats/GroupStatsContainer.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
'use client'

// This component is a work in progress. It is not intended to be imported as-is, but is currently being used for initial GroupStats local development.

import { object, string } from 'prop-types'
import { useState } from 'react'

import {
usePanoptesAuthUser,
usePanoptesProjects,
usePanoptesUserGroup,
useStats
} from '@hooks'

import {
deletePanoptesUserGroup,
getBearerToken,
getDateInterval,
updatePanoptesUserGroup
} from '@utils'

Expand All @@ -24,10 +25,15 @@ function GroupStatsContainer({
authClient,
groupId
}) {
const [selectedProject, setSelectedProject] = useState('AllProjects')
const [selectedDateRange, setSelectedDateRange] = useState('Last7Days')

// fetch authenticated user
const {
data: authUser
} = usePanoptesAuthUser(authClient)

// fetch user_group
const {
data,
error: groupError,
Expand All @@ -37,17 +43,57 @@ function GroupStatsContainer({
authUserId: authUser?.id,
groupId
})
const group = data?.body?.user_groups?.[0]

// fetch all projects stats, used by projects select and top projects regardless of selected project
const allProjectsStatsQuery = getDateInterval(selectedDateRange)
allProjectsStatsQuery.top_contributors = 10

const {
data: groupStats,
error: groupStatsError,
isLoading: groupStatsLoading
data: allProjectsStats,
error: statsError,
isLoading: statsLoading
} = useStats({
authClient,
authUserId: authUser?.id,
endpoint: STATS_ENDPOINT,
sourceId: groupId
sourceId: group?.id,
query: allProjectsStatsQuery
})

// fetch individual project stats
const projectStatsQuery = getDateInterval(selectedDateRange)
projectStatsQuery.project_id = parseInt(selectedProject)
projectStatsQuery.top_contributors = 10

const {
data: projectStats,
error: projectStatsError,
isLoading: projectStatsLoading
} = useStats({
authClient,
authUserId: authUser?.id,
endpoint: STATS_ENDPOINT,
sourceId: group?.id,
query: projectStatsQuery
})

// fetch projects
const projectIDs = allProjectsStats?.project_contributions?.map(project => project.project_id)

const {
data: projects,
error: projectsError,
isLoading: projectsLoading
} = usePanoptesProjects(projectIDs)

function handleProjectSelect (project) {
setSelectedProject(project.value)
}

function handleDateRangeSelect (dateRange) {
setSelectedDateRange(dateRange.value)
}

async function getRequestHeaders() {
const authorization = await getBearerToken(authClient)
Expand Down Expand Up @@ -80,14 +126,18 @@ function GroupStatsContainer({
}
}

const group = data?.body?.user_groups?.[0]

return (
<GroupStats
allProjectsStats={allProjectsStats}
group={group}
groupStats={groupStats}
handleDateRangeSelect={handleDateRangeSelect}
handleGroupDelete={handleGroupDelete}
handleGroupUpdate={handleGroupUpdate}
handleProjectSelect={handleProjectSelect}
projectStats={projectStats}
projects={projects}
selectedDateRange={selectedDateRange}
selectedProject={selectedProject}
/>
)
}
Expand Down
4 changes: 2 additions & 2 deletions packages/lib-user/src/components/UserStats/UserStats.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { arrayOf, func, number, shape, string } from 'prop-types'

import {
Layout
Layout,
MainContent,
} from '@components/shared'

import MainContent from './components/MainContent'
import TopProjects from './components/TopProjects'

const DEFAULT_HANDLER = () => true
Expand Down

This file was deleted.

2 changes: 1 addition & 1 deletion packages/lib-user/src/components/shared/Layout/Layout.js
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ function Layout ({ children }) {
dark: 'dark-3',
light: 'neutral-6'
}}
gap='32px'
gap='30px'
>
{children}
</PageBody>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { composeStory } from '@storybook/react'
import { render, screen } from '@testing-library/react'

import { USER } from '../../../../../test/mocks/panoptes'
import { STATS } from '../../../../../test/mocks/stats.mock.js'
import { USER } from '../../../../test/mocks/panoptes'
import { STATS } from '../../../../test/mocks/stats.mock.js'

import Meta, { Default } from './MainContent.stories.js'

Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { Box } from 'grommet'

import { PROJECTS, USER } from '../../../../../test/mocks/panoptes'
import { STATS } from '../../../../../test/mocks/stats.mock.js'
import { PROJECTS, USER } from '../../../../test/mocks/panoptes'
import { STATS } from '../../../../test/mocks/stats.mock'

import MainContent from './MainContent'

export default {
title: 'Components/UserStats/MainContent',
title: 'Components/shared/MainContent',
component: MainContent,
decorators: [ComponentDecorator]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './MainContent.js'
1 change: 1 addition & 0 deletions packages/lib-user/src/components/shared/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export { default as BarChart } from './BarChart'
export { default as ContentBox } from './ContentBox'
export { default as Layout } from './Layout'
export { default as MainContent } from './MainContent'
export { default as ProfileHeader } from './ProfileHeader'
export { default as ProjectCard } from './ProjectCard'
export { default as Select } from './Select'
Expand Down
4 changes: 1 addition & 3 deletions packages/lib-user/src/utils/getDateInterval.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ function getPeriodFromDateDifference(difference) {
}
}

function getDateInterval(dateRange) {
export function getDateInterval(dateRange) {
const endDate = new Date()
const end_date = endDate.toISOString().substring(0, 10)

Expand Down Expand Up @@ -81,5 +81,3 @@ function getDateInterval(dateRange) {
}
}
}

export { getDateInterval }

0 comments on commit f2bb696

Please sign in to comment.