From 4e1f716f27b706523d6ff4b81a0ac32921714dfd Mon Sep 17 00:00:00 2001 From: Delilah <23665803+goplayoutside3@users.noreply.github.com> Date: Fri, 15 Nov 2024 12:19:57 -0600 Subject: [PATCH 01/16] create new YourProjectStats component with useSWR for data --- .../src/screens/ClassifyPage/ClassifyPage.js | 3 +- .../YourProjectStats/YourProjectStats.js | 47 +++++++ .../YourProjectStats/YourProjectStats.spec.js | 0 .../YourProjectStats.stories.js | 8 ++ .../YourProjectStats/useYourProjectStats.js | 119 ++++++++++++++++++ .../src/shared/components/Stat/Stat.js | 26 ++-- .../Dashboard/DashboardContainer.js | 1 + 7 files changed, 195 insertions(+), 9 deletions(-) create mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js create mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.spec.js create mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js create mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js diff --git a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js index e1b084dae2..c9443d0b41 100644 --- a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js +++ b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js @@ -10,6 +10,7 @@ import ProjectStatistics from '@shared/components/ProjectStatistics' import FinishedForTheDay from './components/FinishedForTheDay' import RecentSubjects from './components/RecentSubjects' import YourStats from './components/YourStats' +import YourProjectStats from './components/YourProjectStats/YourProjectStats.js' import StandardLayout from '@shared/components/StandardLayout' import WorkflowAssignmentModal from './components/WorkflowAssignmentModal' import WorkflowMenuModal from './components/WorkflowMenuModal' @@ -115,7 +116,7 @@ function ClassifyPage({ columns={screenSize === 'small' ? ['auto'] : ['1fr', '2fr']} gap={screenSize === 'small' ? 'small' : 'medium'} > - + diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js new file mode 100644 index 0000000000..b04edbafa3 --- /dev/null +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js @@ -0,0 +1,47 @@ +import { Box } from 'grommet' +import { useContext } from 'react' +import { MobXProviderContext, observer } from 'mobx-react' + +import Stat from '@shared/components/Stat' +import useYourProjectStats from './useYourProjectStats.js' + +function storeMapper(store) { + const { project, user } = store + + return { + projectID: project.id, + userID: user.id + } +} + +function YourProjectStats() { + const { store } = useContext(MobXProviderContext) + const { projectID, userID } = storeMapper(store) + + const { data, loading, error } = useYourProjectStats({ projectID, userID }) + // console.log(data) + + return ( + <> + {userID ? ( + <> + {error ? ( + There was an error loading your stats + ) : ( + + + + + )} + + ) : ( + + + + + )} + + ) +} + +export default observer(YourProjectStats) diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.spec.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.spec.js new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js new file mode 100644 index 0000000000..50dade6109 --- /dev/null +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js @@ -0,0 +1,8 @@ +import YourProjectStats from './YourProjectStats' + +export default { + title: 'Classify / YourProjectStats', + component: YourProjectStats, +} + +export const Default = {} diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js new file mode 100644 index 0000000000..6dd79fcd7b --- /dev/null +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js @@ -0,0 +1,119 @@ +import useSWR from 'swr' +import { usePanoptesAuth } from '@hooks' +import { env, panoptes } from '@zooniverse/panoptes-js' +import getServerSideAPIHost from '@helpers/getServerSideAPIHost' +import logToSentry from '@helpers/logger/logToSentry.js' + + +const SWROptions = { + revalidateIfStale: true, + revalidateOnMount: true, + revalidateOnFocus: true, + revalidateOnReconnect: true, + refreshInterval: 0 +} + +function statsHost(env) { + switch (env) { + case 'production': + return 'https://eras.zooniverse.org' + default: + return 'https://eras-staging.zooniverse.org' + } +} + +const endpoint = '/classifications/users' + +/* user.created_at is needed for allTimeQuery, and not always available on the logged in user object */ +async function fetchUserCreatedAt(userID) { + const { headers, host } = getServerSideAPIHost(env) + const userQuery = { + env, + id: userID + } + try { + const response = await panoptes.get(`/users`, userQuery, { ...headers }, host) + if (response.ok) { + return response.body.users[0].created_at.substring(0, 10) + } + } catch (error) { + console.error('Error loading user with id:', userID) + logToSentry(error) + } +} + +/* Same technique as getDefaultDateRange() in lib-user */ +function formatSevenDaysStatsQuery() { + const today = new Date() + const todayDateString = today.toISOString().substring(0, 10) + + const defaultStartDate = new Date() + const sevenDaysAgo = defaultStartDate.getUTCDate() - 6 + defaultStartDate.setUTCDate(sevenDaysAgo) + const endDateString = defaultStartDate.toISOString().substring(0, 10) + + const query = { + end_date: todayDateString, // "Today" in UTC Timezone + period: 'day', + start_date: endDateString // 7 Days ago + } + + const statsQuery = new URLSearchParams(query).toString() + return statsQuery +} + +/* Similar to getDateInterval() and StatsTabs in lib-user */ +function formatAllTimeStatsQuery(userCreatedAt) { + const today = new Date() + const todayDateString = today.toISOString().substring(0, 10) + + let queryPeriod + const differenceInDays = (new Date(todayDateString) - new Date(userCreatedAt)) / (1000 * 60 * 60 * 24) + if (differenceInDays <= 31) queryPeriod = 'day' + else if (differenceInDays <= 183) queryPeriod = 'week' + else if (differenceInDays <= 1460) queryPeriod = 'month' + else queryPeriod = 'year' + + const query = { + end_date: todayDateString, + period: queryPeriod, + start_date: userCreatedAt + } + + const statsQuery = new URLSearchParams(query).toString() + return statsQuery +} + +async function fetchStats({ endpoint, projectID, userID, authorization }) { + const headers = { authorization } + const host = statsHost(env) + + try { + const userCreatedAt = await fetchUserCreatedAt(userID) + + const sevenDaysQuery = formatSevenDaysStatsQuery() + const allTimeQuery = formatAllTimeStatsQuery(userCreatedAt) + + const sevenDaysResponse = await fetch(`${host}${endpoint}/${userID}/?${sevenDaysQuery}&project_id=${projectID}`, { headers }) + const sevenDaysStats = await sevenDaysResponse.json() + + const allTimeResponse = await fetch(`${host}${endpoint}/${userID}/?${allTimeQuery}&project_id=${projectID}`, { headers }) + const allTimeStats = await allTimeResponse.json() + + return { + sevenDaysStats, + allTimeStats + } + } catch (error) { + console.error('Error fetching stats', error) + logToSentry(error) + } +} + +export default function useYourProjectStats({ projectID, userID }) { + const authorization = usePanoptesAuth(userID) + + // only fetch stats when a userID is available. Don't fetch if no user logged in. + const key = authorization && userID ? { endpoint, projectID, userID, authorization } : null + return useSWR(key, fetchStats, SWROptions) +} diff --git a/packages/app-project/src/shared/components/Stat/Stat.js b/packages/app-project/src/shared/components/Stat/Stat.js index 8a3e8ac3df..6bb46613c0 100644 --- a/packages/app-project/src/shared/components/Stat/Stat.js +++ b/packages/app-project/src/shared/components/Stat/Stat.js @@ -3,16 +3,26 @@ import { number, string } from 'prop-types' import AnimatedNumber from '@zooniverse/react-components/AnimatedNumber' -function Stat ({ className, label, value }) { +/* + valueLoading is passed from components with useSWR to + avoid rendering the value in AnimatedNumber while stats + data is still loading +*/ + +function Stat({ className, label, value, valueLoading }) { return (
- - - + {valueLoading ? ( + + ) : ( + + + + )} { export default function DashboardContainer({ authUser }) { const key = { endpoint: '/users/[id]', authUser } const { data: user, isLoading: userLoading } = useSWR(key, fetchProfileBanner, SWROptions) + console.log('lib-user', user) return ( Date: Fri, 15 Nov 2024 13:26:31 -0600 Subject: [PATCH 02/16] create a stats container component and a RequireUser component --- .../YourProjectStats/YourProjectStats.js | 57 ++++++++++++------- .../YourProjectStats.stories.js | 31 +++++++++- .../YourProjectStatsContainer.js | 34 +++++++++++ .../components/RequireUser/RequireUser.js | 16 ++++++ .../RequireUser/RequireUser.stories.js | 8 +++ .../src/shared/components/Stat/Stat.js | 26 +++------ 6 files changed, 129 insertions(+), 43 deletions(-) create mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStatsContainer.js create mode 100644 packages/app-project/src/shared/components/RequireUser/RequireUser.js create mode 100644 packages/app-project/src/shared/components/RequireUser/RequireUser.stories.js diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js index b04edbafa3..daaa8a2d32 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js @@ -1,31 +1,31 @@ import { Box } from 'grommet' -import { useContext } from 'react' -import { MobXProviderContext, observer } from 'mobx-react' +import Loader from '@zooniverse/react-components/Loader' +import { array, bool, number, shape, string } from 'prop-types' import Stat from '@shared/components/Stat' -import useYourProjectStats from './useYourProjectStats.js' +import RequireUser from '@shared/components/RequireUser/RequireUser.js' -function storeMapper(store) { - const { project, user } = store - - return { - projectID: project.id, - userID: user.id +const defaultStatsData = { + allTimeStats: { + period: [], + total_count: 0 + }, + sevenDaysStats: { + period: [], + total_count: 0 } } -function YourProjectStats() { - const { store } = useContext(MobXProviderContext) - const { projectID, userID } = storeMapper(store) - - const { data, loading, error } = useYourProjectStats({ projectID, userID }) - // console.log(data) - +function YourProjectStats({ data = defaultStatsData, loading, error, userID}) { return ( <> {userID ? ( <> - {error ? ( + {loading ? + + + + : error ? ( There was an error loading your stats ) : ( @@ -35,13 +35,26 @@ function YourProjectStats() { )} ) : ( - - - - + )} ) } -export default observer(YourProjectStats) +export default YourProjectStats + +YourProjectStats.propTypes = { + data: shape({ + allTimeStats: shape({ + period: array, + total_count: number + }), + sevenDaysStats: shape({ + period: array, + total_count: number + }) + }), + loading: bool, + error: bool, + userID: string +} diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js index 50dade6109..03d025011a 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js @@ -1,8 +1,33 @@ import YourProjectStats from './YourProjectStats' +const mockData = { + allTimeStats: { + period: [], + total_count: 84 + }, + sevenDaysStats: { + period: [], + total_count: 674328 + } +} + export default { - title: 'Classify / YourProjectStats', - component: YourProjectStats, + title: 'Project App / Screens / Classify / YourProjectStats', + component: YourProjectStats } -export const Default = {} +export const NoUser = { + render: () => +} + +export const WithUser = { + render: () => +} + +export const Loading = { + render: () => +} + +export const Error = { + render: () => +} diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStatsContainer.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStatsContainer.js new file mode 100644 index 0000000000..6896ed5399 --- /dev/null +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStatsContainer.js @@ -0,0 +1,34 @@ +import { useContext } from 'react' +import { MobXProviderContext, observer } from 'mobx-react' + +import useYourProjectStats from './useYourProjectStats.js' +import YourProjectStats from './YourProjectStats.js' + +function storeMapper(store) { + const { project, user } = store + + return { + projectID: project.id, + userID: user.id + } +} + +/** + * This is a relatively simple container for ProjectStats, but data fetching + * and store observing are purposely separated from the presentational component + * styling and logic. Fetching user data requires authorization, making it + * complicated to use a mock library like MSW for useYourProjectStats() hook. +*/ + +function YourProjectStatsContainer() { + const { store } = useContext(MobXProviderContext) + const { projectID, userID } = storeMapper(store) + + const { data, loading, error } = useYourProjectStats({ projectID, userID }) + + return ( + + ) +} + +export default observer(YourProjectStatsContainer) diff --git a/packages/app-project/src/shared/components/RequireUser/RequireUser.js b/packages/app-project/src/shared/components/RequireUser/RequireUser.js new file mode 100644 index 0000000000..f02336e304 --- /dev/null +++ b/packages/app-project/src/shared/components/RequireUser/RequireUser.js @@ -0,0 +1,16 @@ +import { Box } from 'grommet' +import { useTranslation } from 'next-i18next' + +export default function RequireUser() { + const { t } = useTranslation('components') + + return ( + + {t('RequireUser.text')} + + ) +} diff --git a/packages/app-project/src/shared/components/RequireUser/RequireUser.stories.js b/packages/app-project/src/shared/components/RequireUser/RequireUser.stories.js new file mode 100644 index 0000000000..25ba42fa13 --- /dev/null +++ b/packages/app-project/src/shared/components/RequireUser/RequireUser.stories.js @@ -0,0 +1,8 @@ +import RequireUser from './RequireUser' + +export default { + title: 'Project App / shared / RequireUser', + component: RequireUser +} + +export const Default = {} diff --git a/packages/app-project/src/shared/components/Stat/Stat.js b/packages/app-project/src/shared/components/Stat/Stat.js index 6bb46613c0..336a07631a 100644 --- a/packages/app-project/src/shared/components/Stat/Stat.js +++ b/packages/app-project/src/shared/components/Stat/Stat.js @@ -3,26 +3,16 @@ import { number, string } from 'prop-types' import AnimatedNumber from '@zooniverse/react-components/AnimatedNumber' -/* - valueLoading is passed from components with useSWR to - avoid rendering the value in AnimatedNumber while stats - data is still loading -*/ - -function Stat({ className, label, value, valueLoading }) { +function Stat({ className, label, value }) { return (
- {valueLoading ? ( - - ) : ( - - - - )} + + + Date: Fri, 15 Nov 2024 15:18:58 -0600 Subject: [PATCH 03/16] create ClassificationsChart component with VisX --- .../src/screens/ClassifyPage/ClassifyPage.js | 4 +- .../YourProjectStats/YourProjectStats.js | 87 ++++++++--- .../YourProjectStats.stories.js | 25 ++- .../YourProjectStatsContainer.js | 16 +- .../components/ClassificationsChart.js | 143 ++++++++++++++++++ .../ClassificationsChartContainer.js | 76 ++++++++++ .../helpers/dateRangeHelpers.js | 21 +++ .../YourProjectStats/useYourProjectStats.js | 33 ++-- 8 files changed, 351 insertions(+), 54 deletions(-) create mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChart.js create mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChartContainer.js create mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/dateRangeHelpers.js diff --git a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js index c9443d0b41..311aa33740 100644 --- a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js +++ b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js @@ -10,7 +10,7 @@ import ProjectStatistics from '@shared/components/ProjectStatistics' import FinishedForTheDay from './components/FinishedForTheDay' import RecentSubjects from './components/RecentSubjects' import YourStats from './components/YourStats' -import YourProjectStats from './components/YourProjectStats/YourProjectStats.js' +import YourProjectStatsContainer from './components/YourProjectStats/YourProjectStatsContainer.js' import StandardLayout from '@shared/components/StandardLayout' import WorkflowAssignmentModal from './components/WorkflowAssignmentModal' import WorkflowMenuModal from './components/WorkflowMenuModal' @@ -116,7 +116,7 @@ function ClassifyPage({ columns={screenSize === 'small' ? ['auto'] : ['1fr', '2fr']} gap={screenSize === 'small' ? 'small' : 'medium'} > - + diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js index daaa8a2d32..c07606e357 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js @@ -1,43 +1,83 @@ import { Box } from 'grommet' import Loader from '@zooniverse/react-components/Loader' -import { array, bool, number, shape, string } from 'prop-types' +import { arrayOf, bool, object, number, shape, string } from 'prop-types' +import { useTranslation } from 'next-i18next' +import ContentBox from '@shared/components/ContentBox' import Stat from '@shared/components/Stat' import RequireUser from '@shared/components/RequireUser/RequireUser.js' +import ClassificationsChartContainer from './components/ClassificationsChartContainer.js' const defaultStatsData = { allTimeStats: { - period: [], + data: [{ + count: 0, + period: [] + }], total_count: 0 }, sevenDaysStats: { - period: [], + data: [{ + count: 0, + period: [] + }], total_count: 0 } } -function YourProjectStats({ data = defaultStatsData, loading, error, userID}) { +function YourProjectStats({ + data = defaultStatsData, + loading = false, + error = undefined, + projectID = '', + userID = '', + userLogin = '' +}) { + const { t } = useTranslation('screens') + + const linkProps = { + externalLink: true, + href: `https://www.zooniverse.org/users/${userLogin}/stats?project_id=${projectID}` + } + return ( - <> + {userID ? ( <> - {loading ? - - - - : error ? ( - There was an error loading your stats - ) : ( - - - + {loading ? ( + + + + ) : error ? ( + + There was an error loading your stats. + + ) : data ? ( + <> + + + - )} + + + ) : null} ) : ( )} - + ) } @@ -46,15 +86,22 @@ export default YourProjectStats YourProjectStats.propTypes = { data: shape({ allTimeStats: shape({ - period: array, + data: arrayOf(shape({ + count: number, + period: string + })), total_count: number }), sevenDaysStats: shape({ - period: array, + data: arrayOf(shape({ + count: number, + period: string + })), total_count: number }) }), loading: bool, - error: bool, + error: object, + projectID: string, userID: string } diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js index 03d025011a..c558603f5b 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js @@ -3,11 +3,11 @@ import YourProjectStats from './YourProjectStats' const mockData = { allTimeStats: { period: [], - total_count: 84 + total_count: 37564 }, sevenDaysStats: { period: [], - total_count: 674328 + total_count: 84 } } @@ -21,13 +21,28 @@ export const NoUser = { } export const WithUser = { - render: () => + render: () => ( + + ) } export const Loading = { - render: () => + render: () => ( + + ) } export const Error = { - render: () => + render: () => ( + + ) } diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStatsContainer.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStatsContainer.js index 6896ed5399..a06489761e 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStatsContainer.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStatsContainer.js @@ -9,7 +9,8 @@ function storeMapper(store) { return { projectID: project.id, - userID: user.id + userID: user.id, + userLogin: user.login } } @@ -18,16 +19,23 @@ function storeMapper(store) { * and store observing are purposely separated from the presentational component * styling and logic. Fetching user data requires authorization, making it * complicated to use a mock library like MSW for useYourProjectStats() hook. -*/ + */ function YourProjectStatsContainer() { const { store } = useContext(MobXProviderContext) - const { projectID, userID } = storeMapper(store) + const { projectID, userID, userLogin } = storeMapper(store) const { data, loading, error } = useYourProjectStats({ projectID, userID }) return ( - + ) } diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChart.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChart.js new file mode 100644 index 0000000000..7baaadae5e --- /dev/null +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChart.js @@ -0,0 +1,143 @@ +import { arrayOf, string, shape, number } from 'prop-types' +import styled, { css, useTheme } from 'styled-components' +import { AxisBottom, AxisLeft } from '@visx/axis' +import { Group } from '@visx/group' +import { Bar } from '@visx/shape' +import { Text } from '@visx/text' +import { scaleBand, scaleLinear } from '@visx/scale' + +const StyledBarGroup = styled(Group)` + text { + display: none; + ${props => css` + fill: ${props.theme.global.colors['light-1']}; + font-size: ${props.theme.text.small.size}; + `} + } + + &:hover rect, + &:focus rect { + ${props => + css` + fill: ${props.theme.global.colors.brand}; + `} + } + + &:hover text, + &:focus text { + display: inline-block; + } +` + +function ClassificationsChart({ stats = [] }) { + const theme = useTheme() + + const HEIGHT = 300 + const PADDING = 25 + const WIDTH = 500 + const xScale = scaleBand({ + range: [0, WIDTH], + round: true, + domain: stats.map(stat => stat.longLabel), + padding: 0.1 + }) + + const yScale = scaleLinear({ + range: [HEIGHT - PADDING, 0], + round: true, + domain: [0, Math.max(...stats.map(stat => stat.count), 10)], + nice: true + }) + + const axisColour = theme.dark + ? theme.global.colors.text.dark + : theme.global.colors.text.light + + function tickLabelProps() { + return { + 'aria-hidden': 'true', + dx: '-0.25em', + dy: '0.2em', + fill: axisColour, + fontFamily: theme.global.font.family, + fontSize: '1rem', + textAnchor: 'middle' + } + } + + function shortDayLabels(dayName) { + const stat = stats.find(stat => stat.longLabel === dayName) + return stat.label + } + + return ( +
+ + + + {stats.map(stat => { + const barWidth = xScale.bandwidth() + const barHeight = HEIGHT - PADDING - yScale(stat.count) + const barX = xScale(stat.longLabel) + const barY = HEIGHT - PADDING - barHeight + return ( + + + + + ) + })} + + + +
+ ) +} + +ClassificationsChart.propTypes = { + stats: arrayOf( + shape({ + alt: string, + count: number, + label: string, + longLabel: string, + period: string + }) + ) +} + +export default ClassificationsChart diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChartContainer.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChartContainer.js new file mode 100644 index 0000000000..2e80cab00e --- /dev/null +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChartContainer.js @@ -0,0 +1,76 @@ +import ClassificationsChart from './ClassificationsChart.js' +import { arrayOf, number, shape, string } from 'prop-types' +import { useRouter } from 'next/router.js' + +import { getTodayDateString, getSevenDaysAgoDateString } from '../helpers/dateRangeHelpers.js' + +const defaultStatsData = { + data: [{ + count: 0, + period: [] + }], + total_count: 0 +} + +export default function ClassificationsChartContainer({ stats = defaultStatsData }) { + const router = useRouter() + + // Similar to getCompleteData() in lib-user's bar chart. + // The data.period array returned from ERAS includes only days you've classified on + // but we want to display all seven days in the ClassificationsChart + const completeData = [] + let index = 0 + + // Use the same date strings that were used in the sevenDaysAgo ERAS query + const todayDateString = getTodayDateString() + const sevenDaysAgoString = getSevenDaysAgoDateString() + + // Loop to current date starting seven days ago and see if there's matching data returned from ERAS + let currentDate = new Date(sevenDaysAgoString) + const endDate = new Date(todayDateString) + + while (currentDate <= endDate) { + const matchingData = stats.data.find(stat => { + const statPeriod = new Date(stat.period) + const match = currentDate.getUTCDate() === statPeriod.getUTCDate() + return match + }) + + if (matchingData) { + completeData.push({ + index, + ...matchingData + }) + } else { + completeData.push({ + index, + period: currentDate.toISOString(), + count: 0 + }) + } + + currentDate.setUTCDate(currentDate.getUTCDate() + 1) + index += 1 + } + + // Attach 'day of the week' labels to each stat + const statsWithLabels = completeData.map(({ count, period }) => { + const date = new Date(period) + const longLabel = date.toLocaleDateString(router?.locale, { weekday: 'long' }) + const alt = `${longLabel}: ${count}` + const label = date.toLocaleDateString(router?.locale, { weekday: 'short' }) + return { alt, count, label, longLabel, period } + }) + + return +} + +ClassificationsChartContainer.propTypes = { + stats: shape({ + data: arrayOf(shape({ + count: number, + period: string + })), + total_count: number + }) +} diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/dateRangeHelpers.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/dateRangeHelpers.js new file mode 100644 index 0000000000..541327d514 --- /dev/null +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/dateRangeHelpers.js @@ -0,0 +1,21 @@ +/* Today in UTC */ +export function getTodayDateString() { + const today = new Date() + return today.toISOString().substring(0, 10) +} + +export function getSevenDaysAgoDateString() { + const sevenDaysAgoDate = new Date() + const sevenDaysAgo = sevenDaysAgoDate.getUTCDate() - 6 + sevenDaysAgoDate.setUTCDate(sevenDaysAgo) + return sevenDaysAgoDate.toISOString().substring(0, 10) +} + +export function getQueryPeriod(endDate, startDate) { + const differenceInDays = (new Date(endDate) - new Date(startDate)) / (1000 * 60 * 60 * 24) + + if (differenceInDays <= 31) return 'day' + else if (differenceInDays <= 183) return 'week' + else if (differenceInDays <= 1460) return 'month' + else return 'year' +} diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js index 6dd79fcd7b..96469b87e9 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js @@ -1,9 +1,10 @@ import useSWR from 'swr' -import { usePanoptesAuth } from '@hooks' import { env, panoptes } from '@zooniverse/panoptes-js' import getServerSideAPIHost from '@helpers/getServerSideAPIHost' import logToSentry from '@helpers/logger/logToSentry.js' +import { usePanoptesAuth } from '@hooks' +import { getTodayDateString, getSevenDaysAgoDateString, getQueryPeriod } from './helpers/dateRangeHelpers.js' const SWROptions = { revalidateIfStale: true, @@ -44,35 +45,22 @@ async function fetchUserCreatedAt(userID) { /* Same technique as getDefaultDateRange() in lib-user */ function formatSevenDaysStatsQuery() { - const today = new Date() - const todayDateString = today.toISOString().substring(0, 10) - - const defaultStartDate = new Date() - const sevenDaysAgo = defaultStartDate.getUTCDate() - 6 - defaultStartDate.setUTCDate(sevenDaysAgo) - const endDateString = defaultStartDate.toISOString().substring(0, 10) + const todayDateString = getTodayDateString() + const sevenDaysAgoString = getSevenDaysAgoDateString() const query = { - end_date: todayDateString, // "Today" in UTC Timezone + end_date: todayDateString, period: 'day', - start_date: endDateString // 7 Days ago + start_date: sevenDaysAgoString } - const statsQuery = new URLSearchParams(query).toString() - return statsQuery + return new URLSearchParams(query).toString() } /* Similar to getDateInterval() and StatsTabs in lib-user */ function formatAllTimeStatsQuery(userCreatedAt) { - const today = new Date() - const todayDateString = today.toISOString().substring(0, 10) - - let queryPeriod - const differenceInDays = (new Date(todayDateString) - new Date(userCreatedAt)) / (1000 * 60 * 60 * 24) - if (differenceInDays <= 31) queryPeriod = 'day' - else if (differenceInDays <= 183) queryPeriod = 'week' - else if (differenceInDays <= 1460) queryPeriod = 'month' - else queryPeriod = 'year' + const todayDateString = getTodayDateString() + const queryPeriod = getQueryPeriod(todayDateString, userCreatedAt) const query = { end_date: todayDateString, @@ -80,8 +68,7 @@ function formatAllTimeStatsQuery(userCreatedAt) { start_date: userCreatedAt } - const statsQuery = new URLSearchParams(query).toString() - return statsQuery + return new URLSearchParams(query).toString() } async function fetchStats({ endpoint, projectID, userID, authorization }) { From d669f0660742aeb9dee0e22763ebf5286cf772ac Mon Sep 17 00:00:00 2001 From: Delilah <23665803+goplayoutside3@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:16:25 -0600 Subject: [PATCH 04/16] fix styling of ClassificationChart --- .../YourProjectStats/YourProjectStats.js | 77 +++++++++++-------- .../YourProjectStats.stories.js | 30 +++++++- .../components/ClassificationsChart.js | 61 +++++++-------- .../ClassificationsChartContainer.js | 22 ++---- .../helpers/dateRangeHelpers.js | 10 +-- .../YourProjectStats/useYourProjectStats.js | 4 +- 6 files changed, 114 insertions(+), 90 deletions(-) diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js index c07606e357..f7e7f0bc07 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js @@ -1,7 +1,6 @@ import { Box } from 'grommet' import Loader from '@zooniverse/react-components/Loader' import { arrayOf, bool, object, number, shape, string } from 'prop-types' -import { useTranslation } from 'next-i18next' import ContentBox from '@shared/components/ContentBox' import Stat from '@shared/components/Stat' @@ -10,17 +9,21 @@ import ClassificationsChartContainer from './components/ClassificationsChartCont const defaultStatsData = { allTimeStats: { - data: [{ - count: 0, - period: [] - }], + data: [ + { + count: 0, + period: '' + } + ], total_count: 0 }, sevenDaysStats: { - data: [{ - count: 0, - period: [] - }], + data: [ + { + count: 0, + period: '' + } + ], total_count: 0 } } @@ -33,8 +36,6 @@ function YourProjectStats({ userID = '', userLogin = '' }) { - const { t } = useTranslation('screens') - const linkProps = { externalLink: true, href: `https://www.zooniverse.org/users/${userLogin}/stats?project_id=${projectID}` @@ -57,21 +58,27 @@ function YourProjectStats({ There was an error loading your stats.
) : data ? ( - <> - - - + + + + + + - - ) : null} ) : ( @@ -86,17 +93,21 @@ export default YourProjectStats YourProjectStats.propTypes = { data: shape({ allTimeStats: shape({ - data: arrayOf(shape({ - count: number, - period: string - })), + data: arrayOf( + shape({ + count: number, + period: string + }) + ), total_count: number }), sevenDaysStats: shape({ - data: arrayOf(shape({ - count: number, - period: string - })), + data: arrayOf( + shape({ + count: number, + period: string + }) + ), total_count: number }) }), diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js index c558603f5b..ef56684b96 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js @@ -1,13 +1,35 @@ import YourProjectStats from './YourProjectStats' +import { + getTodayDateString, + getNumDaysAgoDateString +} from './helpers/dateRangeHelpers' + +// Mock stats data of a user who classified on three days in the past seven days +const sevenDaysAgoString = getNumDaysAgoDateString(6) +const threeDaysAgoString = getNumDaysAgoDateString(2) +const todayDateString = getTodayDateString() + const mockData = { allTimeStats: { - period: [], - total_count: 37564 + total_count: 9436 }, sevenDaysStats: { - period: [], - total_count: 84 + data: [ + { + count: 5, + period: sevenDaysAgoString + }, + { + count: 23, + period: threeDaysAgoString + }, + { + count: 12, + period: todayDateString + } + ], + total_count: 40 } } diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChart.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChart.js index 7baaadae5e..72398b1a70 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChart.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChart.js @@ -7,14 +7,6 @@ import { Text } from '@visx/text' import { scaleBand, scaleLinear } from '@visx/scale' const StyledBarGroup = styled(Group)` - text { - display: none; - ${props => css` - fill: ${props.theme.global.colors['light-1']}; - font-size: ${props.theme.text.small.size}; - `} - } - &:hover rect, &:focus rect { ${props => @@ -25,10 +17,19 @@ const StyledBarGroup = styled(Group)` &:hover text, &:focus text { - display: inline-block; + display: block; } ` +const StyledBarLabel = styled(Text)` + display: none; // hide until bar is hovered or focused + + ${props => css` + fill: ${props.theme.global.colors['neutral-1']}; + font-size: 1rem; + `} +` + function ClassificationsChart({ stats = [] }) { const theme = useTheme() @@ -50,19 +51,17 @@ function ClassificationsChart({ stats = [] }) { }) const axisColour = theme.dark - ? theme.global.colors.text.dark - : theme.global.colors.text.light + ? theme.global.colors.white + : theme.global.colors.black - function tickLabelProps() { - return { - 'aria-hidden': 'true', - dx: '-0.25em', - dy: '0.2em', - fill: axisColour, - fontFamily: theme.global.font.family, - fontSize: '1rem', - textAnchor: 'middle' - } + const tickLabelProps = { + 'aria-hidden': 'true', + dx: '-0.25em', + dy: '0.2em', + fill: axisColour, + fontFamily: theme.global.font.family, + fontSize: '1rem', + textAnchor: 'middle' } function shortDayLabels(dayName) { @@ -73,10 +72,8 @@ function ClassificationsChart({ stats = [] }) { return (
- + ) })} @@ -118,6 +110,11 @@ function ClassificationsChart({ stats = [] }) { { const date = new Date(period) - const longLabel = date.toLocaleDateString(router?.locale, { weekday: 'long' }) + const longLabel = date.toLocaleDateString(router?.locale, { timeZone: 'UTC', weekday: 'long' }) const alt = `${longLabel}: ${count}` - const label = date.toLocaleDateString(router?.locale, { weekday: 'short' }) + const label = date.toLocaleDateString(router?.locale, { timeZone: 'UTC', weekday: 'short' }) return { alt, count, label, longLabel, period } }) diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/dateRangeHelpers.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/dateRangeHelpers.js index 541327d514..9e250f3d71 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/dateRangeHelpers.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/dateRangeHelpers.js @@ -4,11 +4,11 @@ export function getTodayDateString() { return today.toISOString().substring(0, 10) } -export function getSevenDaysAgoDateString() { - const sevenDaysAgoDate = new Date() - const sevenDaysAgo = sevenDaysAgoDate.getUTCDate() - 6 - sevenDaysAgoDate.setUTCDate(sevenDaysAgo) - return sevenDaysAgoDate.toISOString().substring(0, 10) +export function getNumDaysAgoDateString(numOfDays) { + const daysAgoDate = new Date() + const daysAgo = daysAgoDate.getUTCDate() - numOfDays + daysAgoDate.setUTCDate(daysAgo) + return daysAgoDate.toISOString().substring(0, 10) } export function getQueryPeriod(endDate, startDate) { diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js index 96469b87e9..2766f0dd46 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js @@ -4,7 +4,7 @@ import getServerSideAPIHost from '@helpers/getServerSideAPIHost' import logToSentry from '@helpers/logger/logToSentry.js' import { usePanoptesAuth } from '@hooks' -import { getTodayDateString, getSevenDaysAgoDateString, getQueryPeriod } from './helpers/dateRangeHelpers.js' +import { getTodayDateString, getNumDaysAgoDateString, getQueryPeriod } from './helpers/dateRangeHelpers.js' const SWROptions = { revalidateIfStale: true, @@ -46,7 +46,7 @@ async function fetchUserCreatedAt(userID) { /* Same technique as getDefaultDateRange() in lib-user */ function formatSevenDaysStatsQuery() { const todayDateString = getTodayDateString() - const sevenDaysAgoString = getSevenDaysAgoDateString() + const sevenDaysAgoString = getNumDaysAgoDateString(6) const query = { end_date: todayDateString, From abe26aae8b26da8c83141f46bd44dcf27f3cad92 Mon Sep 17 00:00:00 2001 From: Delilah <23665803+goplayoutside3@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:48:39 -0600 Subject: [PATCH 05/16] remove all the legacy YourStats code --- .../src/screens/ClassifyPage/ClassifyPage.js | 1 - .../screens/ClassifyPage/ClassifyPage.spec.js | 6 +- .../components/YourStats/YourStats.js | 59 ------- .../components/YourStats/YourStats.mock.js | 68 -------- .../components/YourStats/YourStats.spec.js | 46 ------ .../components/YourStats/YourStats.stories.js | 35 ---- .../YourStats/YourStatsContainer.js | 28 ---- .../DailyClassificationsChart.js | 153 ------------------ .../DailyClassificationsChartConnector.js | 44 ----- .../DailyClassificationsChartContainer.js | 48 ------ .../DailyClassificationsChart/index.js | 1 - .../components/YourStats/index.js | 1 - .../components/withRequireUser/README.md | 3 - .../components/withRequireUser/index.js | 1 - .../withRequireUser/withRequireUser.js | 42 ----- .../withRequireUser/withRequireUser.mock.js | 24 --- .../withRequireUser/withRequireUser.spec.js | 39 ----- .../withRequireUser.stories.js | 26 --- packages/app-project/stores/Store.spec.js | 21 +-- .../stores/User/Recents/Recents.spec.js | 3 - packages/app-project/stores/User/User.spec.js | 27 ++-- .../Notifications/Notifications.spec.js | 3 - .../UserPersonalization.js | 39 +---- .../UserPersonalization.spec.js | 139 ++-------------- .../UserProjectPreferences.js | 12 +- .../UserProjectPreferences.spec.js | 24 --- .../YourStats/YourStats.js | 121 -------------- .../YourStats/YourStats.spec.js | 131 --------------- .../UserPersonalization/YourStats/index.js | 1 - packages/app-project/stores/initStore.js | 2 +- 30 files changed, 36 insertions(+), 1112 deletions(-) delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.mock.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.spec.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.stories.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStatsContainer.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/DailyClassificationsChart.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/DailyClassificationsChartConnector.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/DailyClassificationsChartContainer.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/index.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourStats/index.js delete mode 100644 packages/app-project/src/shared/components/withRequireUser/README.md delete mode 100644 packages/app-project/src/shared/components/withRequireUser/index.js delete mode 100644 packages/app-project/src/shared/components/withRequireUser/withRequireUser.js delete mode 100644 packages/app-project/src/shared/components/withRequireUser/withRequireUser.mock.js delete mode 100644 packages/app-project/src/shared/components/withRequireUser/withRequireUser.spec.js delete mode 100644 packages/app-project/src/shared/components/withRequireUser/withRequireUser.stories.js delete mode 100644 packages/app-project/stores/User/UserPersonalization/YourStats/YourStats.js delete mode 100644 packages/app-project/stores/User/UserPersonalization/YourStats/YourStats.spec.js delete mode 100644 packages/app-project/stores/User/UserPersonalization/YourStats/index.js diff --git a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js index 311aa33740..031b1f70ee 100644 --- a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js +++ b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js @@ -9,7 +9,6 @@ import ConnectWithProject from '@shared/components/ConnectWithProject' import ProjectStatistics from '@shared/components/ProjectStatistics' import FinishedForTheDay from './components/FinishedForTheDay' import RecentSubjects from './components/RecentSubjects' -import YourStats from './components/YourStats' import YourProjectStatsContainer from './components/YourProjectStats/YourProjectStatsContainer.js' import StandardLayout from '@shared/components/StandardLayout' import WorkflowAssignmentModal from './components/WorkflowAssignmentModal' diff --git a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.spec.js b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.spec.js index c15b31ea04..7ae4b8a823 100644 --- a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.spec.js +++ b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.spec.js @@ -3,7 +3,7 @@ import { shallow } from 'enzyme' import { ClassifyPage, ClassifierWrapper } from './ClassifyPage' import FinishedForTheDay from './components/FinishedForTheDay' import WorkflowMenuModal from './components/WorkflowMenuModal' -import YourStats from './components/YourStats' +import YourProjectStatsContainer from './components/YourProjectStats/YourProjectStatsContainer.js' import ConnectWithProject from '@shared/components/ConnectWithProject' import ProjectStatistics from '@shared/components/ProjectStatistics' import WorkflowAssignmentModal from './components/WorkflowAssignmentModal' @@ -29,8 +29,8 @@ describe('Component > ClassifyPage', function () { expect(wrapper.find(ProjectStatistics)).to.have.lengthOf(1) }) - it('should render the `YourStats` component', function () { - expect(wrapper.find(YourStats)).to.have.lengthOf(1) + it('should render the `YourProjectStatsContainer` component', function () { + expect(wrapper.find(YourProjectStatsContainer)).to.have.lengthOf(1) }) it('should render the `ConnectWithProject` component', function () { diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.js deleted file mode 100644 index 71c4740b84..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.js +++ /dev/null @@ -1,59 +0,0 @@ -import { Box, Grid, Paragraph } from 'grommet' -import { number, shape, string } from 'prop-types' -import { useTranslation } from 'next-i18next' - -import DailyClassificationsChart from './components/DailyClassificationsChart' -import ContentBox from '@shared/components/ContentBox' -import Stat from '@shared/components/Stat' - -const defaultCounts = { - today: 0, - total: 0 -} - -function YourStats({ - counts = defaultCounts, - projectName = '' -}) { - const { t } = useTranslation('screens') - - return ( - - - {t('Classify.YourStats.text')} - - - - - - - - - - - - ) -} - -YourStats.propTypes = { - counts: shape({ - today: number, - total: number - }), - projectName: string -} - -export default YourStats diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.mock.js b/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.mock.js deleted file mode 100644 index f5a3aeef10..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.mock.js +++ /dev/null @@ -1,68 +0,0 @@ -const YourStatsStoreMock = { - project: { - display_name: 'Stats Mock Project' - }, - user: { - personalization: { - counts: { - today: 97, - total: 100 - }, - stats: { - thisWeek: [ - { - alt: 'Monday: 85', - count: 85, - period: '2019-09-30', - label: 'M', - longLabel: 'Monday' - }, - { - alt: 'Tuesday: 97', - count: 97, - period: '2019-10-1', - label: 'T', - longLabel: 'Tuesday' - }, - { - alt: 'Wednesday: 0', - count: 0, - period: '2019-10-2', - label: 'W', - longLabel: 'Wednesday' - }, - { - alt: 'Thursday: 0', - count: 0, - period: '2019-10-3', - label: 'T', - longLabel: 'Thursday' - }, - { - alt: 'Friday: 0', - count: 0, - period: '2019-10-4', - label: 'F', - longLabel: 'Friday' - }, - { - alt: 'Saturday: 0', - count: 0, - period: '2019-10-5', - label: 'S', - longLabel: 'Saturday' - }, - { - alt: 'Sunday: 0', - count: 0, - period: '2019-10-6', - label: 'S', - longLabel: 'Sunday' - } - ] - } - } - }, -} - -export { YourStatsStoreMock } diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.spec.js b/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.spec.js deleted file mode 100644 index 110f71646a..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.spec.js +++ /dev/null @@ -1,46 +0,0 @@ -import { render, screen } from '@testing-library/react' -import { composeStory } from '@storybook/react' -import sinon from 'sinon' - -import Meta, { YourStats } from './YourStats.stories.js' -import { YourStatsStoreMock } from './YourStats.mock' - -describe('Component > YourStats', function () { - beforeEach(function () { - const YourStatsStory = composeStory(YourStats, Meta) - render() - }) - - it('should have a heading', function () { - expect(screen.getByText('Classify.YourStats.title')).to.be.ok() - }) - - it('should have today\'s classification count ', async function () { - expect(document.getElementsByClassName('test-classify-your-stats-today-count').length).to.equal(1) - }) - - it('should have this week\'s classification total ', function () { - expect(document.getElementsByClassName('test-classify-your-stats-total-count').length).to.equal(1) - }) - - it('should have a chart heading', function () { - expect(screen.getByText('Classify.YourStats.DailyClassificationsChart.title')).to.be.ok() - }) - - it('should have 7 bars and each bar should be an image', function () { - expect(screen.getAllByRole('img').length).to.equal(7) - }) -}) - -describe('Component > YourStats > Daily bars', function () { - YourStatsStoreMock.user.personalization.stats.thisWeek.forEach(function (count, i) { - it(`should have an accessible description for ${count.longLabel}`, function () { - const clock = sinon.useFakeTimers(new Date('2019-10-01T19:00:00+06:00')) - const YourStatsStory = composeStory(YourStats, Meta) - render() - const bar = screen.getByRole('img', { name: count.alt }) - expect(bar).to.exist() - clock.restore() - }) - }) -}) diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.stories.js b/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.stories.js deleted file mode 100644 index a87c45b87f..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStats.stories.js +++ /dev/null @@ -1,35 +0,0 @@ -import { Provider } from 'mobx-react' -import { RouterContext } from 'next/dist/shared/lib/router-context.shared-runtime' -import { Grommet, base } from 'grommet' -import { YourStatsContainer } from './YourStatsContainer' -import { YourStatsStoreMock } from './YourStats.mock' - -const mockedRouter = { - asPath: '/projects/zooniverse/snapshot-serengeti/about/team', - query: { - owner: 'zooniverse', - project: 'snapshot-serengeti' - } -} - -function NextRouterStory(Story) { - return ( - - - - ) -} - -export default { - title: 'Project App / Screens / Classify / Your Stats', - component: YourStatsContainer, - decorators: [NextRouterStory] -} - -export const YourStats = () => ( - - - - - -) diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStatsContainer.js b/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStatsContainer.js deleted file mode 100644 index d9b23ff359..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/YourStats/YourStatsContainer.js +++ /dev/null @@ -1,28 +0,0 @@ -import { MobXProviderContext, observer } from 'mobx-react' -import { useContext } from 'react' - -import YourStats from './YourStats' -import withRequireUser from '@shared/components/withRequireUser' - -function useStores() { - const stores = useContext(MobXProviderContext) - const store = stores.store - const { - project, - user: { - personalization: { counts } - } - } = store - return { - counts, - projectName: project['display_name'] - } -} - -function YourStatsContainer() { - const { counts, projectName } = useStores() - return -} - -export default withRequireUser(observer(YourStatsContainer)) -export { YourStatsContainer } diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/DailyClassificationsChart.js b/packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/DailyClassificationsChart.js deleted file mode 100644 index eaf402d612..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/DailyClassificationsChart.js +++ /dev/null @@ -1,153 +0,0 @@ -import { array, object, string } from 'prop-types' -import styled, { css, withTheme } from 'styled-components' -import { AxisBottom, AxisLeft } from '@visx/axis' -import { Group } from '@visx/group' -import { Bar } from '@visx/shape' -import { Text } from '@visx/text' -import { scaleBand, scaleLinear } from '@visx/scale' -import WidgetHeading from '@shared/components/WidgetHeading' -import { useTranslation } from 'next-i18next' - -const StyledBarGroup = styled(Group)` - text { - display: none; - ${props => css` - fill: ${props.theme.global.colors['light-1']}; - font-size: ${props.theme.text.small.size} - `} - } - - &:hover rect, - &:focus rect { - ${props => css`fill: ${props.theme.global.colors.brand};`} - } - - &:hover text, - &:focus text { - display: inline-block; - } -` - -function DailyClassificationsChart ({ stats, projectName, theme }) { - const { t } = useTranslation('screens') - - const HEIGHT = 200 - const PADDING = 20 - const WIDTH = 300 - const xScale = scaleBand({ - range: [0, WIDTH], - round: true, - domain: stats.map(stat => stat.longLabel), - padding: 0.1 - }) - - const yScale = scaleLinear({ - range: [HEIGHT - PADDING, 0], - round: true, - domain: [0, Math.max(...stats.map(stat => stat.count), 10)], - nice: true - }) - - const axisColour = theme.dark - ? theme.global.colors.text.dark - : theme.global.colors.text.light - - function tickLabelProps () { - return { - 'aria-hidden': 'true', - dx: '-0.25em', - dy: '0.2em', - fill: axisColour, - fontFamily: theme.global.font.family, - fontSize: 10, - textAnchor: 'middle' } - } - function shortDayLabels (dayName) { - const stat = stats.find(stat => stat.longLabel === dayName) - return stat.label - } - return ( - <> - - {t('Classify.YourStats.DailyClassificationsChart.title', { projectName })} - - - - - {stats.map(stat => { - const barWidth = xScale.bandwidth() - const barHeight = (HEIGHT - PADDING) - yScale(stat.count) - const barX = xScale(stat.longLabel) - const barY = (HEIGHT - PADDING) - barHeight - return ( - - - - - ) - })} - - - - - ) -} - -DailyClassificationsChart.propTypes = { - stats: array, - projectName: string.isRequired, - theme: object -} - -DailyClassificationsChart.defaultProps = { - stats: [], - theme: { - global: { - colors: { - text: {} - } - }, - text: { - small: {} - } - } -} - -export default withTheme(DailyClassificationsChart) -export { DailyClassificationsChart } diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/DailyClassificationsChartConnector.js b/packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/DailyClassificationsChartConnector.js deleted file mode 100644 index 3efbb29f2c..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/DailyClassificationsChartConnector.js +++ /dev/null @@ -1,44 +0,0 @@ -import { MobXProviderContext, observer } from 'mobx-react' -import { useRouter } from 'next/router' -import { useContext } from 'react' - -import DailyClassificationsChartContainer from './DailyClassificationsChartContainer' - -function storeMapper(store) { - const { - project, - user: { - personalization: { - counts, - stats: { - thisWeek - } - } - } - } = store - - return { - counts, - thisWeek, - projectName: project['display_name'] - } -} - -function DailyClassificationsChartConnector() { - const { store } = useContext(MobXProviderContext) - const { counts, thisWeek, projectName } = storeMapper(store) - const router = useRouter() - const locale = router?.locale - const sanitizedLocale = locale === 'test' ? 'en' : locale - - return ( - - ) -} - -export default observer(DailyClassificationsChartConnector) \ No newline at end of file diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/DailyClassificationsChartContainer.js b/packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/DailyClassificationsChartContainer.js deleted file mode 100644 index c950b9e979..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/DailyClassificationsChartContainer.js +++ /dev/null @@ -1,48 +0,0 @@ -import { array, number, shape, string } from 'prop-types' - -import DailyClassificationsChart from './DailyClassificationsChart' - -const defaultCounts = { - today: 0 -} - -function DailyClassificationsChartContainer({ - counts = defaultCounts, - locale = 'en', - projectName, - thisWeek = [] -}) { - const TODAY = new Date() - const stats = thisWeek.map(({ count: statsCount, period }) => { - const [year, monthIndex, date] = period.split('-') - const utcDay = Date.UTC(year, monthIndex - 1, date) - const day = new Date(utcDay) - const isToday = day.getUTCDay() === TODAY.getDay() - const count = isToday ? counts.today : statsCount - const longLabel = day.toLocaleDateString(locale, { timeZone: 'UTC', weekday: 'long' }) - const alt = `${longLabel}: ${count}` - const label = day.toLocaleDateString(locale, { timeZone: 'UTC', weekday: 'narrow' }) - return { alt, count, label, longLabel, period } - }) - return ( - - ) -} - -DailyClassificationsChartContainer.propTypes = { - /** Today's counts, updated as classifications are made. */ - counts: shape({ - today: number - }), - /** The current locale. */ - locale: string, - /** Project name */ - projectName: string.isRequired, - /** Array of daily stats from the stats server */ - thisWeek: array -} - -export default DailyClassificationsChartContainer diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/index.js b/packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/index.js deleted file mode 100644 index ae0ca3f734..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/YourStats/components/DailyClassificationsChart/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './DailyClassificationsChartConnector' diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourStats/index.js b/packages/app-project/src/screens/ClassifyPage/components/YourStats/index.js deleted file mode 100644 index fd6aec0553..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/YourStats/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './YourStatsContainer' diff --git a/packages/app-project/src/shared/components/withRequireUser/README.md b/packages/app-project/src/shared/components/withRequireUser/README.md deleted file mode 100644 index b8516d5594..0000000000 --- a/packages/app-project/src/shared/components/withRequireUser/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# withRequireUser - -A HOC that wraps its component in a Grommet ``, and adds an overlay asking the user to sign in over the top. diff --git a/packages/app-project/src/shared/components/withRequireUser/index.js b/packages/app-project/src/shared/components/withRequireUser/index.js deleted file mode 100644 index dfc90f0c9c..0000000000 --- a/packages/app-project/src/shared/components/withRequireUser/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './withRequireUser' diff --git a/packages/app-project/src/shared/components/withRequireUser/withRequireUser.js b/packages/app-project/src/shared/components/withRequireUser/withRequireUser.js deleted file mode 100644 index 275cf5fff3..0000000000 --- a/packages/app-project/src/shared/components/withRequireUser/withRequireUser.js +++ /dev/null @@ -1,42 +0,0 @@ -import { Box, Stack } from 'grommet' -import { observer, MobXProviderContext } from 'mobx-react' -import { useContext } from 'react' -import { useTranslation } from 'next-i18next' - -function useStores(mockStore) { - const stores = useContext(MobXProviderContext) - const store = mockStore || stores.store - const { - user: { isLoggedIn } - } = store - return { - isLoggedIn - } -} - -export default function withRequireUser(WrappedComponent) { - function ComponentConnector({ mockStore, ...props }) { - const { t } = useTranslation('components') - const { isLoggedIn } = useStores(mockStore) - - return ( - - - {!isLoggedIn && ( - - - {t('RequireUser.text')} - - - )} - - ) - } - - return observer(ComponentConnector) -} diff --git a/packages/app-project/src/shared/components/withRequireUser/withRequireUser.mock.js b/packages/app-project/src/shared/components/withRequireUser/withRequireUser.mock.js deleted file mode 100644 index f1c6d6ed34..0000000000 --- a/packages/app-project/src/shared/components/withRequireUser/withRequireUser.mock.js +++ /dev/null @@ -1,24 +0,0 @@ -const withRequireUserLoggedInStoreMock = { - user: { - isLoggedIn: true - } -} - -const withRequireUserLoggedOutStoreMock = { - user: { - isLoggedIn: false - } -} - -const withRequireUserText = 'With Require User' - -function withRequireUserStubComponent () { - return

{withRequireUserText}

-} - -export { - withRequireUserLoggedOutStoreMock, - withRequireUserLoggedInStoreMock, - withRequireUserText, - withRequireUserStubComponent -} diff --git a/packages/app-project/src/shared/components/withRequireUser/withRequireUser.spec.js b/packages/app-project/src/shared/components/withRequireUser/withRequireUser.spec.js deleted file mode 100644 index 5c60d53dde..0000000000 --- a/packages/app-project/src/shared/components/withRequireUser/withRequireUser.spec.js +++ /dev/null @@ -1,39 +0,0 @@ -import { render, screen } from '@testing-library/react' -import { withRequireUserText } from './withRequireUser.mock' -import { composeStory } from '@storybook/react' -import Meta, { LoggedIn, LoggedOut } from './withRequireUser.stories' - -describe('withRequireUser', function () { - describe('behavior when logged in', function () { - beforeEach(function () { - const LoggedInStory = composeStory(LoggedIn, Meta) - render() - }) - - it('should render the wrapped component', function () { - expect(screen.getByText(withRequireUserText)).to.exist() - }) - - it('shouldn\'t include a message to log in', function () { - // doesn't have translation so we use the key - expect(screen.queryByText('RequireUser.text')).to.be.null() - }) - }) - - describe('behavior when not logged in', function () { - beforeEach(function () { - const LoggedOutStory = composeStory(LoggedOut, Meta) - render() - }) - - it('should render the wrapped component', function () { - expect(screen.getByText(withRequireUserText)).to.exist() - }) - - it('should include a message to log in', function () { - // doesn't have translation so we use the key - expect(screen.queryByText('RequireUser.text')).to.exist() - }) - }) - -}) diff --git a/packages/app-project/src/shared/components/withRequireUser/withRequireUser.stories.js b/packages/app-project/src/shared/components/withRequireUser/withRequireUser.stories.js deleted file mode 100644 index 397cf0c10a..0000000000 --- a/packages/app-project/src/shared/components/withRequireUser/withRequireUser.stories.js +++ /dev/null @@ -1,26 +0,0 @@ -import { Provider } from 'mobx-react' -import withRequireUser from './withRequireUser' -import { - withRequireUserLoggedInStoreMock, - withRequireUserLoggedOutStoreMock, - withRequireUserStubComponent -} from './withRequireUser.mock' - -const WithRequireUser = withRequireUser(withRequireUserStubComponent) - -export default { - title: 'Project App / Shared / withRequireUser', - component: WithRequireUser -} - -export const LoggedIn = () => ( - - - -) - -export const LoggedOut = () => ( - - - -) diff --git a/packages/app-project/stores/Store.spec.js b/packages/app-project/stores/Store.spec.js index d8b4470cfa..7cff976675 100644 --- a/packages/app-project/stores/Store.spec.js +++ b/packages/app-project/stores/Store.spec.js @@ -56,7 +56,7 @@ describe('Stores > Store', function () { }) describe('when a user signs out', function () { - it('should reset the user project preferences and stats', function () { + it('should reset the user project preferences and sessionCount', function () { const store = Store.create({ user: { id: '1', @@ -64,10 +64,6 @@ describe('Stores > Store', function () { login: 'zootester', personalization: { projectPreferences: { - activity_count: 8, - activity_count_by_workflow: { - 1234: 8, - }, id: '5', links: { project: '5678', user: '1' }, loadingState: asyncStates.success, @@ -77,20 +73,7 @@ describe('Stores > Store', function () { } }, settings: { workflow_id: '4444' } - }, - stats: { - loadingState: asyncStates.success, - thisWeek: [ - { count: 12, dayNumber: 1, period: '2019-09-29' }, - { count: 12, dayNumber: 2, period: '2019-09-30' }, - { count: 13, dayNumber: 3, period: '2019-10-01' }, - { count: 14, dayNumber: 4, period: '2019-10-02' }, - { count: 10, dayNumber: 5, period: '2019-10-03' }, - { count: 11, dayNumber: 6, period: '2019-10-04' }, - { count: 8, dayNumber: 0, period: '2019-10-05' } - ] - }, - totalClassificationCount: 8 + } } } }, placeholderEnv) diff --git a/packages/app-project/stores/User/Recents/Recents.spec.js b/packages/app-project/stores/User/Recents/Recents.spec.js index bad9953cd7..30df485901 100644 --- a/packages/app-project/stores/User/Recents/Recents.spec.js +++ b/packages/app-project/stores/User/Recents/Recents.spec.js @@ -4,7 +4,6 @@ import { getSnapshot } from 'mobx-state-tree' import auth from 'panoptes-client/lib/auth' import initStore from '@stores/initStore' -import { statsClient } from '../UserPersonalization/YourStats' describe('Stores > Recents', function () { let rootStore @@ -39,13 +38,11 @@ describe('Stores > Recents', function () { collections: [] } })) - sinon.stub(statsClient, 'fetchDailyStats').callsFake(() => Promise.resolve(null)) }) after(function () { console.error.restore() rootStore.client.panoptes.get.restore() - statsClient.fetchDailyStats.restore() }) it('should exist', function () { diff --git a/packages/app-project/stores/User/User.spec.js b/packages/app-project/stores/User/User.spec.js index 32ddc03ea3..0920cdf8e6 100644 --- a/packages/app-project/stores/User/User.spec.js +++ b/packages/app-project/stores/User/User.spec.js @@ -3,9 +3,6 @@ import * as client from '@zooniverse/panoptes-js' import { expect } from 'chai' import { when } from 'mobx' import nock from 'nock' -import sinon from 'sinon' - -import { statsClient } from './UserPersonalization/YourStats' import Store from '@stores/Store' @@ -19,8 +16,6 @@ describe('stores > User', function () { let userStore beforeEach(function () { - sinon.stub(statsClient, 'fetchDailyStats') - nock('https://panoptes-staging.zooniverse.org/api') .persist() .get('/users/1/recents') @@ -37,19 +32,31 @@ describe('stores > User', function () { .get('/project_preferences?project_id=1&user_id=1&http_cache=true') .reply(200, { project_preferences: [ - { activity_count: 23 } + { + links: { + user: '1' + } + } ] }) .get('/project_preferences?project_id=1&user_id=1&http_cache=true') .reply(200, { project_preferences: [ - { activity_count: 25 } + { + links: { + user: '1' + } + } ] }) .get('/project_preferences?project_id=1&user_id=2&http_cache=true') .reply(200, { project_preferences: [ - { activity_count: 27 } + { + links: { + user: '1' + } + } ] }) @@ -70,7 +77,6 @@ describe('stores > User', function () { }) afterEach(function () { - statsClient.fetchDailyStats.restore() nock.cleanAll() }) @@ -105,7 +111,6 @@ describe('stores > User', function () { }) describe('with an existing user session', function () { - it('should refresh project preferences for the same user', async function () { userStore.set(user) const { personalization } = userStore @@ -134,4 +139,4 @@ describe('stores > User', function () { expect(rootStore.appLoadingState).to.equal(asyncStates.loading) }) }) -}) \ No newline at end of file +}) diff --git a/packages/app-project/stores/User/UserPersonalization/Notifications/Notifications.spec.js b/packages/app-project/stores/User/UserPersonalization/Notifications/Notifications.spec.js index 069e286db8..e7cb73e36d 100644 --- a/packages/app-project/stores/User/UserPersonalization/Notifications/Notifications.spec.js +++ b/packages/app-project/stores/User/UserPersonalization/Notifications/Notifications.spec.js @@ -6,7 +6,6 @@ import asyncStates from '@zooniverse/async-states' import { talkAPI } from '@zooniverse/panoptes-js' import initStore from '@stores/initStore' -import { statsClient } from '../YourStats' import Notifications from './Notifications' describe('Stores > Notifications', function () { @@ -19,14 +18,12 @@ describe('Stores > Notifications', function () { } before(function () { - sinon.stub(statsClient, 'fetchDailyStats') sinon.stub(sugarClient, 'subscribeTo') sinon.stub(sugarClient, 'on') sinon.stub(sugarClient, 'unsubscribeFrom') }) after(function () { - statsClient.fetchDailyStats.restore() sugarClient.subscribeTo.restore() sugarClient.on.restore() sugarClient.unsubscribeFrom.restore() diff --git a/packages/app-project/stores/User/UserPersonalization/UserPersonalization.js b/packages/app-project/stores/User/UserPersonalization/UserPersonalization.js index 2efce2f105..cc2da609fa 100644 --- a/packages/app-project/stores/User/UserPersonalization/UserPersonalization.js +++ b/packages/app-project/stores/User/UserPersonalization/UserPersonalization.js @@ -4,49 +4,16 @@ import { autorun } from 'mobx' import Notifications from './Notifications' import UserProjectPreferences from './UserProjectPreferences' -import YourStats from './YourStats' const UserPersonalization = types .model('UserPersonalization', { notifications: types.optional(Notifications, {}), projectPreferences: types.optional(UserProjectPreferences, {}), sessionCount: types.optional(types.number, 0), - stats: types.optional(YourStats, {}) }) .views(self => ({ - get counts() { - const today = self.todaysCount - - return { - today, - total: self.totalClassificationCount - } - }, - get sessionCountIsDivisibleByFive() { return self.sessionCount % 5 === 0 - }, - - get todaysCount() { - let todaysCount = 0 - try { - todaysCount = self.todaysStats.count - } catch (error) { - todaysCount = self.sessionCount - } - return todaysCount - }, - - get todaysStats() { - const todaysDate = new Date() - if (self.stats.thisWeek.length === 7) { - return self.stats.thisWeek.find(stat => stat.dayNumber === todaysDate.getDay()) - } - return null - }, - - get totalClassificationCount() { - return self.projectPreferences?.activity_count || 0 } })) .actions(self => { @@ -64,8 +31,6 @@ const UserPersonalization = types increment() { self.sessionCount = self.sessionCount + 1 - self.todaysStats?.increment() - self.projectPreferences?.incrementActivityCount() const { user } = getRoot(self) if (user?.id && self.sessionCountIsDivisibleByFive) { self.projectPreferences.refreshSettings() @@ -75,7 +40,6 @@ const UserPersonalization = types load(newUser = true) { self.notifications.fetchAndSubscribe() if (newUser) { - self.stats.fetchDailyCounts() self.projectPreferences.fetchResource() } else { self.projectPreferences.refreshSettings() @@ -86,10 +50,9 @@ const UserPersonalization = types self.notifications.reset() self.projectPreferences.reset() self.projectPreferences.setLoadingState(asyncStates.success) - self.stats.reset() self.sessionCount = 0 } } }) -export default UserPersonalization \ No newline at end of file +export default UserPersonalization diff --git a/packages/app-project/stores/User/UserPersonalization/UserPersonalization.spec.js b/packages/app-project/stores/User/UserPersonalization/UserPersonalization.spec.js index e84012f78a..8cc5701cb9 100644 --- a/packages/app-project/stores/User/UserPersonalization/UserPersonalization.spec.js +++ b/packages/app-project/stores/User/UserPersonalization/UserPersonalization.spec.js @@ -7,7 +7,6 @@ import { talkAPI } from '@zooniverse/panoptes-js' import initStore from '@stores/initStore' import UserPersonalization from './UserPersonalization' -import { statsClient } from './YourStats' describe('Stores > UserPersonalization', function () { let rootStore, nockScope @@ -23,23 +22,13 @@ describe('Stores > UserPersonalization', function () { before(function () { sinon.stub(console, 'error') - const MOCK_DAILY_COUNTS = [ - { count: 12, period: '2019-09-29' }, - { count: 12, period: '2019-09-30' }, - { count: 13, period: '2019-10-01' }, - { count: 14, period: '2019-10-02' }, - { count: 10, period: '2019-10-03' }, - { count: 11, period: '2019-10-04' }, - { count: 8, period: '2019-10-05' }, - { count: 15, period: '2019-10-06' } - ] nockScope = nock('https://panoptes-staging.zooniverse.org/api') .persist() .get('/project_preferences') .query(true) .reply(200, { project_preferences: [ - { activity_count: 23, id: '5' } + { id: '5' } ] }) .get('/collections') // This is to get the collections store to not make real requests @@ -56,16 +45,12 @@ describe('Stores > UserPersonalization', function () { rootStore = initStore(true, { project }) sinon.spy(rootStore.client.panoptes, 'get') - sinon.stub(statsClient, 'fetchDailyStats').callsFake(({ projectId, userId }) => (projectId === '2' && userId === '123') ? - Promise.resolve({ data: MOCK_DAILY_COUNTS }) : - Promise.reject(new Error(`Unable to fetch stats for project ${projectId} and user ${userId}`))) sinon.stub(talkAPI, 'get').callsFake(() => Promise.resolve(undefined)) }) after(function () { console.error.restore() rootStore.client.panoptes.get.restore() - statsClient.fetchDailyStats.restore() talkAPI.get.restore() nock.cleanAll() }) @@ -98,10 +83,6 @@ describe('Stores > UserPersonalization', function () { expect(rootStore.client.panoptes.get.withArgs(endpoint, query, { authorization })).to.have.been.calledOnce() }) - it('should trigger the child YourStats node to request user statistics', function () { - expect(statsClient.fetchDailyStats).to.have.been.calledOnceWith({ projectId: '2', userId: '123' }) - }) - it('should trigger the child Notifications store to request unread notifications', function () { expect(talkAPI.get).to.have.been.calledWith( '/notifications', @@ -122,25 +103,16 @@ describe('Stores > UserPersonalization', function () { expect(true).to.be.false() }) - describe('incrementing your classification count', function () { + describe('incrementing your session count', function () { before(function () { const user = { id: '123', - login: 'test.user', - personalization: { - projectPreferences: { - activity_count: 23 - } - } + login: 'test.user' } rootStore = initStore(true, { project, user }) rootStore.user.personalization.increment() }) - it('should add 1 to your total count', function () { - expect(rootStore.user.personalization.totalClassificationCount).to.equal(24) - }) - it('should add 1 to your session count', function () { expect(rootStore.user.personalization.sessionCount).to.equal(1) }) @@ -176,7 +148,6 @@ describe('Stores > UserPersonalization', function () { login: 'test.user', personalization: { projectPreferences: { - activity_count: 23, id: '5' } } @@ -200,8 +171,8 @@ describe('Stores > UserPersonalization', function () { expect(rootStore.client.panoptes.get).to.have.not.been.called() }) - it('should start counting from 0', function () { - expect(rootStore.user.personalization.totalClassificationCount).to.equal(0) + it('should start session count from 0', function () { + expect(rootStore.user.personalization.sessionCount).to.equal(0) }) it('should not trigger the child Notifications store to request unread notifications or conversations', function () { @@ -220,17 +191,7 @@ describe('Stores > UserPersonalization', function () { }) }) - describe('incrementing your classification count', function () { - before(function () { - rootStore.user.personalization.increment() - }) - - it('should add 1 to your total count', function () { - expect(rootStore.user.personalization.totalClassificationCount).to.equal(1) - }) - }) - - describe('incrementing your classification count to 5', function () { + describe('incrementing your session count to 5', function () { before(function () { rootStore.client.panoptes.get.resetHistory() rootStore.user.personalization.reset() @@ -241,10 +202,6 @@ describe('Stores > UserPersonalization', function () { rootStore.user.personalization.increment() }) - it('should add 5 to your total count', function () { - expect(rootStore.user.personalization.totalClassificationCount).to.equal(5) - }) - it('should not trigger the child UPP store to request user preferences from Panoptes', function () { expect(rootStore.client.panoptes.get).to.have.not.been.called() }) @@ -267,8 +224,8 @@ describe('Stores > UserPersonalization', function () { auth.checkBearerToken.restore() }) - it('should count session classifications from 0', function () { - expect(rootStore.user.personalization.totalClassificationCount).to.equal(1) + it('should start sessionCount from 0', function () { + expect(rootStore.user.personalization.sessionCount).to.equal(1) }) }) @@ -288,88 +245,19 @@ describe('Stores > UserPersonalization', function () { }) it('should count session classifications from 0', function () { - expect(rootStore.user.personalization.totalClassificationCount).to.equal(1) + expect(rootStore.user.personalization.sessionCount).to.equal(1) }) }) - describe('counts view', function () { - it('should return the expected counts with no data', function () { - const personalizationStore = UserPersonalization.create() - expect(personalizationStore.counts).to.deep.equal({ - today: 0, - total: 0 - }) - }) - - describe('total count', function () { - it('should get the total count from the store `totalClassificationCount` value', function () { - const personalizationStore = UserPersonalization.create() - personalizationStore.increment() - personalizationStore.increment() - personalizationStore.increment() - personalizationStore.increment() - expect(personalizationStore.counts.total).to.equal(4) - }) - }) - - describe('today\'s count', function () { - let clock - - before(function () { - clock = sinon.useFakeTimers({ now: new Date(2019, 9, 1, 12), toFake: ['Date'] }) - }) - - after(function () { - clock.restore() - }) - - it('should get today\'s count from the store\'s counts for this week', function () { - const MOCK_DAILY_COUNTS = [ - { count: 12, dayNumber: 1, period: '2019-09-30T00:00:00Z' }, - { count: 13, dayNumber: 2, period: '2019-10-01T00:00:00Z' }, - { count: 14, dayNumber: 3, period: '2019-10-02T00:00:00Z' }, - { count: 10, dayNumber: 4, period: '2019-10-03T00:00:00Z' }, - { count: 11, dayNumber: 5, period: '2019-10-04T00:00:00Z' }, - { count: 8, dayNumber: 6, period: '2019-10-05T00:00:00Z' }, - { count: 15, dayNumber: 0, period: '2019-10-06T00:00:00Z' } - ] - const personalizationStore = UserPersonalization.create({ stats: { thisWeek: MOCK_DAILY_COUNTS } }) - expect(personalizationStore.counts.today).to.equal(MOCK_DAILY_COUNTS[1].count) - }) - - it('should be `0` if there are no classifications today', function () { - const MOCK_DAILY_COUNTS = [ - { count: 12, dayNumber: 2, period: '2019-01-03T00:00:00Z' }, - { count: 13, dayNumber: 1, period: '2019-01-02T00:00:00Z' }, - { count: 14, dayNumber: 0, period: '2019-01-01T00:00:00Z' } - ] - const personalizationStore = UserPersonalization.create({ stats: { thisWeek: MOCK_DAILY_COUNTS } }) - expect(personalizationStore.counts.today).to.equal(0) - }) - }) - }) describe('on reset', function () { - it('should reset project preferences, stats, counts, and notifications', function () { - const MOCK_DAILY_COUNTS = [ - { count: 12, dayNumber: 1, period: '2019-09-30T00:00:00Z' }, - { count: 13, dayNumber: 2, period: '2019-10-01T00:00:00Z' }, - { count: 14, dayNumber: 3, period: '2019-10-02T00:00:00Z' }, - { count: 10, dayNumber: 4, period: '2019-10-03T00:00:00Z' }, - { count: 11, dayNumber: 5, period: '2019-10-04T00:00:00Z' }, - { count: 8, dayNumber: 6, period: '2019-10-05T00:00:00Z' }, - { count: 15, dayNumber: 0, period: '2019-10-06T00:00:00Z' } - ] + it('should reset project preferences, sessionCount, and notifications', function () { const personalizationStore = UserPersonalization.create({ notifications: { unreadConversationsIds: ['246', '357'], unreadNotificationsCount: 5 }, projectPreferences: { - activity_count: 8, - activity_count_by_workflow: { - 1234: 8, - }, id: '5', links: { project: '5678', user: '1' }, loadingState: asyncStates.success, @@ -379,12 +267,7 @@ describe('Stores > UserPersonalization', function () { } }, settings: { workflow_id: '4444' } - }, - stats: { - loadingState: asyncStates.success, - thisWeek: MOCK_DAILY_COUNTS - }, - totalClassificationCount: 8 + } }) personalizationStore.increment() // increment to have a session count const signedInUserPersonalization = personalizationStore.toJSON() diff --git a/packages/app-project/stores/User/UserPersonalization/UserProjectPreferences/UserProjectPreferences.js b/packages/app-project/stores/User/UserPersonalization/UserProjectPreferences/UserProjectPreferences.js index 897b4bf53c..5f71395ce0 100644 --- a/packages/app-project/stores/User/UserPersonalization/UserProjectPreferences/UserProjectPreferences.js +++ b/packages/app-project/stores/User/UserPersonalization/UserProjectPreferences/UserProjectPreferences.js @@ -29,8 +29,6 @@ export const Settings = types const UserProjectPreferences = types .model('UserProjectPreferences', { - activity_count: types.optional(types.number, 0), - activity_count_by_workflow: types.maybe(types.frozen()), error: types.maybeNull(types.frozen({})), id: types.maybe(numberString), links: types.maybe( @@ -81,8 +79,6 @@ const UserProjectPreferences = types return { reset() { const resetSnapshot = { - activity_count: 0, - activity_count_by_workflow: undefined, error: undefined, id: undefined, links: undefined, @@ -129,12 +125,8 @@ const UserProjectPreferences = types } catch (error) { console.error(error) } - }), - - incrementActivityCount() { - self.activity_count = self.activity_count + 1 - } + }) } }) -export default UserProjectPreferences \ No newline at end of file +export default UserProjectPreferences diff --git a/packages/app-project/stores/User/UserPersonalization/UserProjectPreferences/UserProjectPreferences.spec.js b/packages/app-project/stores/User/UserPersonalization/UserProjectPreferences/UserProjectPreferences.spec.js index 8c4b2f3259..96b9978918 100644 --- a/packages/app-project/stores/User/UserPersonalization/UserProjectPreferences/UserProjectPreferences.spec.js +++ b/packages/app-project/stores/User/UserPersonalization/UserProjectPreferences/UserProjectPreferences.spec.js @@ -6,7 +6,6 @@ import asyncStates from '@zooniverse/async-states' import { talkAPI } from '@zooniverse/panoptes-js' import initStore from '@stores/initStore' -import { statsClient } from '../YourStats' import UserProjectPreferences, { Settings } from './UserProjectPreferences' import { expect } from 'chai' @@ -30,8 +29,6 @@ describe('Stores > UserProjectPreferences', function () { } } const initialState = { - activity_count: 0, - activity_count_by_workflow: undefined, error: undefined, id: undefined, links: undefined, @@ -40,8 +37,6 @@ describe('Stores > UserProjectPreferences', function () { settings: undefined } const upp = { - activity_count: 23, - activity_count_by_workflow: {}, id: '555', links: { project: '2', @@ -72,12 +67,10 @@ describe('Stores > UserProjectPreferences', function () { } before(function () { - sinon.stub(statsClient, 'fetchDailyStats') sinon.stub(talkAPI, 'get').resolves([]) }) after(function () { - statsClient.fetchDailyStats.restore() talkAPI.get.restore() }) @@ -152,10 +145,6 @@ describe('Stores > UserProjectPreferences', function () { const storedUPP = Object.assign({}, upp, { error: undefined, loadingState: asyncStates.success }) expect(getSnapshot(projectPreferences)).to.deep.equal(storedUPP) }) - - it('should set the total classification count on the parent node', function () { - expect(rootStore.user.personalization.totalClassificationCount).to.equal(23) - }) }) describe('when there are no user project preferences in the response', function () { @@ -188,10 +177,6 @@ describe('Stores > UserProjectPreferences', function () { expect(projectPreferences.loadingState).to.equal(asyncStates.success) expect(projectPreferences.id).to.be.undefined() }) - - it('should not set the total classification count on the parent node', function () { - expect(rootStore.user.personalization.totalClassificationCount).to.equal(0) - }) }) describe('when the request errors', function () { @@ -269,8 +254,6 @@ describe('Stores > UserProjectPreferences', function () { beforeEach(function () { const personalization = { projectPreferences: { - activity_count: 28, - activity_count_by_workflow: {}, id: '555', loadingState: asyncStates.success } @@ -294,13 +277,6 @@ describe('Stores > UserProjectPreferences', function () { projectPreferences.refreshSettings() }) - it('should not change your total classification count', async function () { - expect(rootStore.user.personalization.totalClassificationCount).to.equal(28) - const { projectPreferences } = rootStore.user.personalization - await when(() => projectPreferences.assignedWorkflowID) - expect(rootStore.user.personalization.totalClassificationCount).to.equal(28) - }) - it('should not change the app loading state', function () { expect(rootStore.appLoadingState).to.equal(asyncStates.success) }) diff --git a/packages/app-project/stores/User/UserPersonalization/YourStats/YourStats.js b/packages/app-project/stores/User/UserPersonalization/YourStats/YourStats.js deleted file mode 100644 index 6e1aeb2746..0000000000 --- a/packages/app-project/stores/User/UserPersonalization/YourStats/YourStats.js +++ /dev/null @@ -1,121 +0,0 @@ -import asyncStates from '@zooniverse/async-states' -import { flow, getRoot, types } from 'mobx-state-tree' -import auth from 'panoptes-client/lib/auth' -import { env } from '@zooniverse/panoptes-js' - -function statsHost(env) { - switch (env) { - case 'production': - return 'https://eras.zooniverse.org' - default: - return 'https://eras-staging.zooniverse.org' - } -} - -export const statsClient = { - async fetchDailyStats({ projectId, userId }) { - const token = await auth.checkBearerToken() - const Authorization = `Bearer ${token}` - const stats = statsHost(env) - const queryParams = new URLSearchParams({ - period: 'day', - project_id: projectId - }).toString() - - const response = await fetch(`${stats}/classifications/users/${userId}?${queryParams}`, { headers: { Authorization } }) - const jsonResponse = await response.json() - return jsonResponse - } -} - -// https://stackoverflow.com/a/51918448/10951669 -function firstDayOfWeek (dateObject, firstDayOfWeekIndex) { - const dayOfWeek = dateObject.getUTCDay() - const firstDayOfWeek = new Date(dateObject) - const diff = dayOfWeek >= firstDayOfWeekIndex - ? dayOfWeek - firstDayOfWeekIndex - : 6 - dayOfWeek - - firstDayOfWeek.setUTCDate(dateObject.getUTCDate() - diff) - - return firstDayOfWeek -} - -const Count = types - .model('Count', { - count: types.number, - dayNumber: types.number, - period: types.string - }) - .actions(self => ({ - increment() { - self.count = self.count + 1 - } - })) - -const YourStats = types - .model('YourStats', { - error: types.maybeNull(types.frozen({})), - loadingState: types.optional(types.enumeration('state', asyncStates.values), asyncStates.initialized), - thisWeek: types.array(Count), - }) - - .actions(self => { - function calculateWeeklyStats (dailyCounts) { - /* - Calculate daily stats for this week, starting last Monday. - */ - const today = new Date() - const weeklyStats = [] - const monday = firstDayOfWeek(today, 1) // Monday is day number 1 in JavaScript - for (let day = 0; day < 7; day++) { - const weekDay = new Date(monday.toISOString()) - const newDate = monday.getUTCDate() + day - weekDay.setUTCDate(newDate) - const period = weekDay.toISOString().substring(0, 10) - const { count } = dailyCounts.find(count => count.period.startsWith(period)) || { count: 0, period } - const dayNumber = weekDay.getDay() - weeklyStats.push({ - count, - dayNumber, - period - }) - } - return weeklyStats - } - - return { - fetchDailyCounts: flow(function * fetchDailyCounts () { - const { project, user } = getRoot(self) - self.setLoadingState(asyncStates.loading) - let dailyCounts - try { - const statsData = yield statsClient.fetchDailyStats({ projectId: project.id, userId: user.id }) - dailyCounts = statsData.data - self.setLoadingState(asyncStates.success) - } catch (error) { - self.handleError(error) - dailyCounts = [] - } - self.thisWeek = calculateWeeklyStats(dailyCounts) - }), - - handleError(error) { - console.error(error) - self.error = error - self.setLoadingState(asyncStates.error) - }, - - setLoadingState(state) { - self.loadingState = state - }, - - reset() { - const emptyStats = calculateWeeklyStats([]) - self.thisWeek = emptyStats - this.setLoadingState(asyncStates.initialized) - } - } - }) - -export default YourStats diff --git a/packages/app-project/stores/User/UserPersonalization/YourStats/YourStats.spec.js b/packages/app-project/stores/User/UserPersonalization/YourStats/YourStats.spec.js deleted file mode 100644 index 7cd621454b..0000000000 --- a/packages/app-project/stores/User/UserPersonalization/YourStats/YourStats.spec.js +++ /dev/null @@ -1,131 +0,0 @@ -import { expect } from 'chai' -import { getSnapshot } from 'mobx-state-tree' -import nock from 'nock' -import sinon from 'sinon' -import asyncStates from '@zooniverse/async-states' -import { talkAPI } from '@zooniverse/panoptes-js' - -import initStore from '@stores/initStore' -import YourStats, { statsClient } from './YourStats' - -describe('Stores > YourStats', function () { - let rootStore, nockScope - const project = { - id: '2', - display_name: 'Hello', - slug: 'test/project' - } - - before(function () { - sinon.stub(console, 'error') - - const MOCK_DAILY_COUNTS = [ - { count: 12, period: '2019-09-29' }, - { count: 12, period: '2019-09-30' }, - { count: 13, period: '2019-10-01' }, - { count: 14, period: '2019-10-02' }, - { count: 10, period: '2019-10-03' }, - { count: 11, period: '2019-10-04' }, - { count: 8, period: '2019-10-05' }, - { count: 15, period: '2019-10-06' } - ] - - nockScope = nock('https://panoptes-staging.zooniverse.org/api') - .persist() - .get('/project_preferences') - .query(true) - .reply(200, { - project_preferences: [ - { activity_count: 23 } - ] - }) - .get('/collections') - .query(true) - .reply(200) - .post('/collections') - .query(true) - .reply(200) - rootStore = initStore(true, { project }) - sinon.stub(statsClient, 'fetchDailyStats').callsFake(({ projectId, userId }) => (projectId === '2' && userId === '123') ? - Promise.resolve({ data: MOCK_DAILY_COUNTS }) : - Promise.reject(new Error(`Unable to fetch stats for project ${projectId} and user ${userId}`))) - sinon.stub(talkAPI, 'get') - }) - - after(function () { - console.error.restore() - statsClient.fetchDailyStats.restore() - talkAPI.get.restore() - nock.cleanAll() - }) - - it('should exist', function () { - expect(rootStore.user.personalization.stats).to.be.ok() - }) - - describe('Actions > fetchDailyCounts', function () { - let clock - - before(function () { - clock = sinon.useFakeTimers({ now: new Date(2019, 9, 1, 12), toFake: ['Date'] }) - const user = { - id: '123', - login: 'test.user' - } - - rootStore.user.set(user) - }) - - after(function () { - clock.restore() - }) - - it('should request user statistics', function () { - expect(statsClient.fetchDailyStats).to.have.been.calledOnceWith({ projectId: '2', userId: '123' }) - }) - - describe('weekly classification stats', function () { - it('should be created', function () { - expect(getSnapshot(rootStore.user.personalization.stats.thisWeek).length).to.equal(7) - }) - - it('should start on Monday', function () { - expect(getSnapshot(rootStore.user.personalization.stats.thisWeek[0])).to.deep.equal({ - count: 12, - dayNumber: 1, - period: '2019-09-30' - }) - }) - - it('should end on Sunday', function () { - expect(getSnapshot(rootStore.user.personalization.stats.thisWeek[6])).to.deep.equal({ - count: 15, - dayNumber: 0, - period: '2019-10-06' - }) - }) - }) - }) - - describe('on reset', function () { - it('should reset weekly stats and loading state', function () { - const MOCK_DAILY_COUNTS = [ - { count: 12, dayNumber: 1, period: '2019-09-29' }, - { count: 12, dayNumber: 2, period: '2019-09-30' }, - { count: 13, dayNumber: 3, period: '2019-10-01' }, - { count: 14, dayNumber: 4, period: '2019-10-02' }, - { count: 10, dayNumber: 5, period: '2019-10-03' }, - { count: 11, dayNumber: 6, period: '2019-10-04' }, - { count: 8, dayNumber: 0, period: '2019-10-05' } - ] - const yourStatsStore = YourStats.create({ - loadingState: asyncStates.success, - thisWeek: MOCK_DAILY_COUNTS - }) - const signedInStats = yourStatsStore.toJSON() - yourStatsStore.reset() - const signedOutStats = yourStatsStore.toJSON() - expect(signedInStats).to.not.deep.equal(signedOutStats) - }) - }) -}) diff --git a/packages/app-project/stores/User/UserPersonalization/YourStats/index.js b/packages/app-project/stores/User/UserPersonalization/YourStats/index.js deleted file mode 100644 index 9e58bf6fed..0000000000 --- a/packages/app-project/stores/User/UserPersonalization/YourStats/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default, statsClient } from './YourStats' diff --git a/packages/app-project/stores/initStore.js b/packages/app-project/stores/initStore.js index a2a0da86cf..968db5d97c 100644 --- a/packages/app-project/stores/initStore.js +++ b/packages/app-project/stores/initStore.js @@ -61,7 +61,7 @@ function initStore (isServer, snapshot = null, client = defaultClient) { if (snapshot) { /* - Don't overwrite the stored user, collections, recents or stats in the browser. + Don't overwrite the stored user, collections, or recents in the browser. Only apply store state that was generated on the server. TODO: won't this overwrite local changes to the UI store? */ From 9468468023e9c858f943b69bbd665bafe419dacc Mon Sep 17 00:00:00 2001 From: Delilah <23665803+goplayoutside3@users.noreply.github.com> Date: Fri, 15 Nov 2024 16:52:02 -0600 Subject: [PATCH 06/16] Delete FinishedForTheDay component per designer request --- .../public/locales/en/screens.json | 10 --- .../public/locales/es/screens.json | 10 --- .../public/locales/fr/screens.json | 13 +-- .../public/locales/pt/screens.json | 13 +-- .../public/locales/test/screens.json | 13 +-- .../public/locales/tr/screens.json | 10 --- .../src/screens/ClassifyPage/ClassifyPage.js | 2 - .../screens/ClassifyPage/ClassifyPage.spec.js | 5 -- .../FinishedForTheDay/FinishedForTheDay.js | 86 ------------------- .../FinishedForTheDay.spec.js | 13 --- .../FinishedForTheDay.stories.js | 19 ---- .../FinishedForTheDayContainer.js | 35 -------- .../components/FinishedForTheDay/index.js | 1 - 13 files changed, 3 insertions(+), 227 deletions(-) delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDay.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDay.spec.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDay.stories.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDayContainer.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/index.js diff --git a/packages/app-project/public/locales/en/screens.json b/packages/app-project/public/locales/en/screens.json index c3732f20f3..9f7aa0e1d3 100644 --- a/packages/app-project/public/locales/en/screens.json +++ b/packages/app-project/public/locales/en/screens.json @@ -55,16 +55,6 @@ "title": "There was an error in the classifier :(" } }, - "FinishedForTheDay": { - "buttons": { - "stats": "See the stats" - }, - "text": "Your answers are saved for the research team while you're working. See the project stats and return to the {{projectName}} home page.", - "title": "Finished for the day?", - "ProjectImage": { - "alt": "Image for {{projectName}}" - } - }, "RecentSubjects": { "text": "Discuss a subject on Talk, or add it to your Favourites or a collection.", "title": "Your recent classifications" diff --git a/packages/app-project/public/locales/es/screens.json b/packages/app-project/public/locales/es/screens.json index 0c30055cf0..c93aaada8d 100644 --- a/packages/app-project/public/locales/es/screens.json +++ b/packages/app-project/public/locales/es/screens.json @@ -30,16 +30,6 @@ "title": "Se ha producido un error en el clasificador :(" } }, - "FinishedForTheDay": { - "ProjectImage": { - "alt": "Imagen para {{projectName}}" - }, - "buttons": { - "stats": "Ver las estadísticas" - }, - "text": "Mientras estás trabajando, tus respuestas se guardan para el equipo de investigación. Mira las estadísticas del proyecto y vuelve a la página de inicio de {{projectName}}.", - "title": "¿Has terminado por hoy?" - }, "RecentSubjects": { "text": "Comenta un objeto en Hablemos o añádelo a tus Favoritos o a una colección.", "title": "Tus clasificaciones recientes" diff --git a/packages/app-project/public/locales/fr/screens.json b/packages/app-project/public/locales/fr/screens.json index 4ec4bd96c8..1605be2973 100644 --- a/packages/app-project/public/locales/fr/screens.json +++ b/packages/app-project/public/locales/fr/screens.json @@ -30,17 +30,6 @@ "title": "Il y a eu une erreur avec le classificateur :(" } }, - "FinishedForTheDay": { - "ProjectImage": { - "alt": "Image pour {{projectName}}" - }, - "buttons": { - "anotherProject": "Trouver un autre projet", - "stats": "Afficher les statistiques" - }, - "text": "Vos réponses sont enregistrées pour l'équipe de recherche pendant que vous travaillez. Consultez les statistiques du projet et revenez à la page d'accueil de {{projectName}}.", - "title": "Terminé pour aujourd'hui?" - }, "RecentSubjects": { "text": "Discutez d'un sujet sur Talk ou ajoutez-le à vos favoris ou à une collection.", "title": "Vos classifications récentes" @@ -87,4 +76,4 @@ "title": "Discussions" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/pt/screens.json b/packages/app-project/public/locales/pt/screens.json index 6f23e7554c..4b09d20783 100644 --- a/packages/app-project/public/locales/pt/screens.json +++ b/packages/app-project/public/locales/pt/screens.json @@ -30,17 +30,6 @@ "title": "Ocorreu um erro no classificador :(" } }, - "FinishedForTheDay": { - "ProjectImage": { - "alt": "Imagem para {{projectName}}" - }, - "buttons": { - "anotherProject": "Encontrar outro projeto", - "stats": "Ver as estatísticas" - }, - "text": "As suas respostas serão guardadas para a equipa de investigação enquanto não estiver a trabalhar. Veja as estatísticas do projeto e volte à página inicial de {{projectName}}.", - "title": "Terminou por hoje?" - }, "RecentSubjects": { "text": "Fale sobre qualquer objeto em Conversar ou adicione-o aos favoritos ou a uma coleção.", "title": "As suas classificações recentes" @@ -87,4 +76,4 @@ "title": "Debater" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/test/screens.json b/packages/app-project/public/locales/test/screens.json index 7ec8006138..2f543a2b4e 100644 --- a/packages/app-project/public/locales/test/screens.json +++ b/packages/app-project/public/locales/test/screens.json @@ -30,17 +30,6 @@ "title": "Translated" } }, - "FinishedForTheDay": { - "ProjectImage": { - "alt": "Translated {{projectName}}" - }, - "buttons": { - "anotherProject": "Translated", - "stats": "Translated" - }, - "text": "Translated {{projectName}} Translated", - "title": "Translated" - }, "RecentSubjects": { "text": "Translated", "title": "Translated" @@ -87,4 +76,4 @@ "title": "Translated" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/tr/screens.json b/packages/app-project/public/locales/tr/screens.json index d23cb5b353..be37e7b6a2 100644 --- a/packages/app-project/public/locales/tr/screens.json +++ b/packages/app-project/public/locales/tr/screens.json @@ -30,16 +30,6 @@ "title": "Sınıflandırıcıda bir hata oluştu :(" } }, - "FinishedForTheDay": { - "ProjectImage": { - "alt": "{{projectName}} için resim" - }, - "buttons": { - "stats": "İstatistikleri görün" - }, - "text": "Siz çalışırken yanıtlarınız araştırma ekibi için kaydedilir. Proje istatistiklerini görün ve {{projectName}} ana sayfasına dönün.", - "title": "Bugünlük işiniz bitti mi?" - }, "RecentSubjects": { "text": "Sohbet'te bir konuyu tartışın veya Favorilerinize ya da bir koleksiyona ekleyin.", "title": "En son çalışmalarınız" diff --git a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js index 031b1f70ee..dfbb5784a3 100644 --- a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js +++ b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js @@ -7,7 +7,6 @@ import withResponsiveContext from '@zooniverse/react-components/helpers/withResp import CollectionsModal from '@shared/components/CollectionsModal' import ConnectWithProject from '@shared/components/ConnectWithProject' import ProjectStatistics from '@shared/components/ProjectStatistics' -import FinishedForTheDay from './components/FinishedForTheDay' import RecentSubjects from './components/RecentSubjects' import YourProjectStatsContainer from './components/YourProjectStats/YourProjectStatsContainer.js' import StandardLayout from '@shared/components/StandardLayout' @@ -110,7 +109,6 @@ function ClassifyPage({ - ClassifyPage', function () { expect(wrapper).to.be.ok() }) - it('should render the `FinishedForTheDay` component', function () { - expect(wrapper.find(FinishedForTheDay)).to.have.lengthOf(1) - }) - it('should render the `ProjectStatistics` component', function () { expect(wrapper.find(ProjectStatistics)).to.have.lengthOf(1) }) diff --git a/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDay.js b/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDay.js deleted file mode 100644 index 615d582fe8..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDay.js +++ /dev/null @@ -1,86 +0,0 @@ -import { Box, Button, Grid, Heading, Paragraph, Text } from 'grommet' -import PropTypes from 'prop-types' -import styled, { withTheme } from 'styled-components' -import { - Media, - ZooniverseLogo -} from '@zooniverse/react-components' -import withResponsiveContext from '@zooniverse/react-components/helpers/withResponsiveContext' -import { useTranslation } from 'next-i18next' - -import ContentBox from '@shared/components/ContentBox' - -const StyledButton = styled(Button)` - border-width: 1px; - flex: 0 1 300px; - margin: 0 10px 10px 0; - text-align: center; -` - -const StyledBox = styled(Box)` - margin: 0 -10px 0 0; - max-width: 620px; -` - -function FinishedForTheDay({ - imageSrc = '', - linkHref, - projectName, - screenSize, - theme: { dark = false } -}) { - const { t } = useTranslation('screens') - - const columns = imageSrc && screenSize !== 'small' ? ['1/4', 'auto'] : ['auto'] - const alt = t('Classify.FinishedForTheDay.ProjectImage.alt', { projectName }) - - return ( - - - - } - width={400} - /> - - - {t('Classify.FinishedForTheDay.title')} - - - - {t('Classify.FinishedForTheDay.text', { projectName })} - - - - - {t('Classify.FinishedForTheDay.buttons.stats')} - - )} - primary - /> - - - - - ) -} - -FinishedForTheDay.propTypes = { - imageSrc: PropTypes.string, - linkProps: PropTypes.string, - projectName: PropTypes.string -} - -export { FinishedForTheDay } -export default withTheme(withResponsiveContext(FinishedForTheDay)) diff --git a/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDay.spec.js b/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDay.spec.js deleted file mode 100644 index 8aac5c1c5f..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDay.spec.js +++ /dev/null @@ -1,13 +0,0 @@ -import { composeStory } from '@storybook/react' -import { render, screen } from '@testing-library/react' -import Meta, { Default } from './FinishedForTheDay.stories.js' - -describe('Component > FinishedForTheDay', function () { - const DefaultStory = composeStory(Default, Meta) - - it('should contain a title and link to stats page', function () { - render() - expect(screen.getByText('Classify.FinishedForTheDay.title')).exists() - expect(screen.getByText('Classify.FinishedForTheDay.buttons.stats')).exists() - }) -}) diff --git a/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDay.stories.js b/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDay.stories.js deleted file mode 100644 index 40a8eecbc7..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDay.stories.js +++ /dev/null @@ -1,19 +0,0 @@ -import FinishedForTheDay from "./FinishedForTheDay" - -export default { - title: 'Project App / Screens / Classify / Finished For The Day', - component: FinishedForTheDay, - args: { - imageSrc: 'https://panoptes-uploads.zooniverse.org/project_background/7a3c6210-f97d-4f40-9ab4-8da30772ee01.jpeg', - linkHref: '', - projectName: 'Mock Project', - screenSize: 'medium', - theme: { - dark: false - } - } -} - -export const Default = (props) => { - return -} diff --git a/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDayContainer.js b/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDayContainer.js deleted file mode 100644 index 9912db5c24..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/FinishedForTheDayContainer.js +++ /dev/null @@ -1,35 +0,0 @@ -import { MobXProviderContext, observer } from 'mobx-react' -import { useRouter } from 'next/router' - -import FinishedForTheDay from './FinishedForTheDay' -import addQueryParams from '@helpers/addQueryParams' -import { useContext } from 'react' - -function useStore () { - const { store } = useContext(MobXProviderContext) - const { project } = store - - return { - imageSrc: project.background.src, - projectName: project['display_name'] - } -} - -const FinishedForTheDayContainer = () => { - const { imageSrc = '', projectName = '' } = useStore() - const router = useRouter() - const owner = router?.query?.owner - const project = router?.query?.project - - const linkHref = addQueryParams(`/projects/${owner}/${project}/stats`) - - return ( - - ) -} - -export default observer(FinishedForTheDayContainer) diff --git a/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/index.js b/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/index.js deleted file mode 100644 index bb280ef1f6..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/FinishedForTheDay/index.js +++ /dev/null @@ -1 +0,0 @@ -export { default } from './FinishedForTheDayContainer' From 6db35bd6088f73afba818c517681ae3a163b321b Mon Sep 17 00:00:00 2001 From: Delilah <23665803+goplayoutside3@users.noreply.github.com> Date: Fri, 15 Nov 2024 17:18:37 -0600 Subject: [PATCH 07/16] fix consistent returns warning --- .../components/YourProjectStats/YourProjectStats.js | 2 +- .../components/YourProjectStats/useYourProjectStats.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js index f7e7f0bc07..dc0ad9e3e0 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js @@ -77,7 +77,7 @@ function YourProjectStats({ valueLoading={loading} /> - + ) : null} diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js index 2766f0dd46..da445eca3f 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js @@ -34,13 +34,12 @@ async function fetchUserCreatedAt(userID) { } try { const response = await panoptes.get(`/users`, userQuery, { ...headers }, host) - if (response.ok) { - return response.body.users[0].created_at.substring(0, 10) - } + return response.body.users[0].created_at.substring(0, 10) } catch (error) { console.error('Error loading user with id:', userID) logToSentry(error) } + return '' } /* Same technique as getDefaultDateRange() in lib-user */ @@ -95,6 +94,7 @@ async function fetchStats({ endpoint, projectID, userID, authorization }) { console.error('Error fetching stats', error) logToSentry(error) } + return null } export default function useYourProjectStats({ projectID, userID }) { From 10a9912bee35ecfc68d86e83f05d06c8de15bebc Mon Sep 17 00:00:00 2001 From: Delilah <23665803+goplayoutside3@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:15:21 -0600 Subject: [PATCH 08/16] add a mutate function for incrementing stats on classification --- .../ClassifierWrapper/ClassifierWrapper.js | 23 +++++++++++-------- .../ClassifierWrapperConnector.js | 5 +--- .../ClassifierWrapperConnector.spec.js | 4 ---- .../helpers/incrementStats.js | 19 +++++++++++++++ 4 files changed, 33 insertions(+), 18 deletions(-) create mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/incrementStats.js diff --git a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js index 0aca565725..3029093813 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js +++ b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js @@ -5,12 +5,14 @@ import { bool, func, string, shape } from 'prop-types' import { useCallback } from 'react' import asyncStates from '@zooniverse/async-states' import { Box } from 'grommet' +import { Loader } from '@zooniverse/react-components' import { useAdminMode } from '@hooks' import addQueryParams from '@helpers/addQueryParams' import logToSentry from '@helpers/logger/logToSentry.js' import ErrorMessage from './components/ErrorMessage' -import { Loader } from '@zooniverse/react-components' +import useYourProjectStats from '../YourProjectStats/useYourProjectStats.js' +import incrementStats from '../YourProjectStats/helpers/incrementStats.js' function onError(error, errorInfo = {}) { logToSentry(error, errorInfo) @@ -43,8 +45,7 @@ export default function ClassifierWrapper({ subjectSetID, user = null, userID, - workflowID, - yourStats + workflowID }) { const { adminMode } = useAdminMode() const nextRouter = useRouter() @@ -53,20 +54,22 @@ export default function ClassifierWrapper({ const ownerSlug = router?.query.owner const projectSlug = router?.query.project - /* Only increment stats on the classify page if the subject is not retired or not already seen by current user */ - const incrementStats = yourStats?.increment + /* + Increment user stats on every classification submitted. + Add the recently classified subject to the user's Recents. + */ + const projectID = project?.id + const { mutate } = useYourProjectStats({ projectID, userID }) + const addRecents = recents?.add const onCompleteClassification = useCallback((classification, subject) => { - const finishedSubject = subject.already_seen || subject.retired - if (!finishedSubject) { - incrementStats() - } + incrementStats(mutate, projectID, userID) addRecents({ favorite: subject.favorite, subjectId: subject.id, locations: subject.locations }) - }, [addRecents, incrementStats]) + }, [addRecents, projectID, userID]) /* If the page URL contains a subject ID, update that ID when the classification subject changes. diff --git a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapperConnector.js b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapperConnector.js index 24c8531a9a..fc0309bebb 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapperConnector.js +++ b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapperConnector.js @@ -22,8 +22,7 @@ function useStore(mockStore) { mode, project, recents: user.recents, - user, - yourStats: user.personalization + user } } @@ -51,7 +50,6 @@ function ClassifierWrapperConnector({ mockStore, ...props }) { project, recents, user, - yourStats } = useStore(mockStore) return ( @@ -66,7 +64,6 @@ function ClassifierWrapperConnector({ mockStore, ...props }) { recents={recents} user={user} userID={user.id} - yourStats={yourStats} {...props} /> ) diff --git a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapperConnector.spec.js b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapperConnector.spec.js index 3c6de7948a..d851793181 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapperConnector.spec.js +++ b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapperConnector.spec.js @@ -41,10 +41,6 @@ describe('Component > ClassifierWrapperConnector', function () { expect(wrapper.props().recents).to.equal(mockStore.user.recents) }) - it('should include your personal stats', function () { - expect(wrapper.props().yourStats).to.equal(mockStore.user.personalization) - }) - it('should include the project', function () { expect(wrapper.props().project).to.deep.equal(mockStore.project.toJSON()) }) diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/incrementStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/incrementStats.js new file mode 100644 index 0000000000..ae434cc0f2 --- /dev/null +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/incrementStats.js @@ -0,0 +1,19 @@ +export default function incrementStats(mutate, projectID, userID) { + if (!projectID || !userID) return + mutate( + prevData => { + const newStats = { + allTimeStats: { + data: prevData.allTimeStats.data, + total_count: prevData.allTimeStats.total_count + 1 + }, + sevenDaysStats: { + data: prevData.sevenDaysStats.data, + total_count: prevData.sevenDaysStats.total_count + 1 + } + } + return newStats + }, + { revalidate: false } // do not refetch stats data while a volunteer classifies on the same project + ) +} From be27129e9fc48e5eda7a4325fc26c077cb0b09f4 Mon Sep 17 00:00:00 2001 From: Delilah <23665803+goplayoutside3@users.noreply.github.com> Date: Tue, 19 Nov 2024 19:06:53 -0600 Subject: [PATCH 09/16] match Figma design and edit translation dictionaries --- .../public/locales/cs/screens.json | 4 +- .../public/locales/de/screens.json | 4 +- .../public/locales/en/screens.json | 11 +- .../public/locales/es/screens.json | 8 +- .../public/locales/fr/screens.json | 8 +- .../public/locales/hi/screens.json | 4 +- .../public/locales/hr/screens.json | 4 +- .../public/locales/it/screens.json | 4 +- .../public/locales/ja/screens.json | 4 +- .../public/locales/nl/screens.json | 4 +- .../public/locales/pl/screens.json | 4 +- .../public/locales/pt/screens.json | 8 +- .../public/locales/ru/screens.json | 4 +- .../public/locales/sv/screens.json | 4 +- .../public/locales/test/screens.json | 8 +- .../public/locales/tr/screens.json | 8 +- .../public/locales/zh-CN/screens.json | 4 +- .../public/locales/zh-TW/screens.json | 4 +- .../src/screens/ClassifyPage/ClassifyPage.js | 2 +- .../RecentSubjects/RecentSubjects.js | 4 +- .../YourProjectStats/YourProjectStats.js | 66 +++++---- .../YourProjectStats.stories.js | 24 --- .../components/ClassificationsChart.js | 140 ------------------ .../ClassificationsChartContainer.js | 70 --------- .../YourProjectStats/useYourProjectStats.js | 2 +- 25 files changed, 77 insertions(+), 330 deletions(-) delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChart.js delete mode 100644 packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChartContainer.js diff --git a/packages/app-project/public/locales/cs/screens.json b/packages/app-project/public/locales/cs/screens.json index a66332ee17..8659810b6a 100644 --- a/packages/app-project/public/locales/cs/screens.json +++ b/packages/app-project/public/locales/cs/screens.json @@ -39,7 +39,7 @@ "dismiss": "Tyto zprávy již nezobrazovat." }, "YourStats": { - "title": "Statistiky projektu {{projectName}}" + "title": "Statistiky" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/de/screens.json b/packages/app-project/public/locales/de/screens.json index f074e9719e..17fbf6f7f6 100644 --- a/packages/app-project/public/locales/de/screens.json +++ b/packages/app-project/public/locales/de/screens.json @@ -38,7 +38,7 @@ "content": "Glückwunsch! Du hast den nächsten Workflow freigeschalten. Wenn Du lieber in diesem Workflow bleiben möchtest, kannst Du auswählen hier zu bleiben." }, "YourStats": { - "title": "{{projectName}} Statistik" + "title": "Statistik" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/en/screens.json b/packages/app-project/public/locales/en/screens.json index 9f7aa0e1d3..168fe9cd2d 100644 --- a/packages/app-project/public/locales/en/screens.json +++ b/packages/app-project/public/locales/en/screens.json @@ -67,13 +67,10 @@ "title": "New Workflow Available" }, "YourStats": { - "todaysCount": "Classifications today", - "totalCount": "Classifications total", - "text": "Keep up the great work!", - "title": "Your {{projectName}} statistics", - "DailyClassificationsChart": { - "title": "{{projectName}} daily classification counts" - } + "allTime": "All Time", + "lastSeven": "Last 7 Days", + "link": "See more", + "title": "Your Stats" } } } diff --git a/packages/app-project/public/locales/es/screens.json b/packages/app-project/public/locales/es/screens.json index c93aaada8d..f791069297 100644 --- a/packages/app-project/public/locales/es/screens.json +++ b/packages/app-project/public/locales/es/screens.json @@ -42,13 +42,7 @@ "title": "Nuevo flujo de trabajo disponible" }, "YourStats": { - "DailyClassificationsChart": { - "title": "Recuentos diarios de la clasificación de {{projectName}}" - }, - "text": "¡Sigue con ese gran trabajo!", - "title": "Estadística de {{projectName}}", - "todaysCount": "Las clasificaciones de hoy", - "totalCount": "Total de clasificaciones" + "title": "Estadística" } }, "Home": { diff --git a/packages/app-project/public/locales/fr/screens.json b/packages/app-project/public/locales/fr/screens.json index 1605be2973..7b3468b9d8 100644 --- a/packages/app-project/public/locales/fr/screens.json +++ b/packages/app-project/public/locales/fr/screens.json @@ -42,13 +42,7 @@ "title": "Nouveau module disponible" }, "YourStats": { - "DailyClassificationsChart": { - "title": "Nombre de classifications quotidiennes de {{projectName}}" - }, - "text": "Continuez votre excellent travail !", - "title": "Statistiques de {{projectName}}", - "todaysCount": "Classifications d'aujourd'hui", - "totalCount": "Total des classifications" + "title": "Statistiques" } }, "Home": { diff --git a/packages/app-project/public/locales/hi/screens.json b/packages/app-project/public/locales/hi/screens.json index 3c68224ca8..67efa1c5e8 100644 --- a/packages/app-project/public/locales/hi/screens.json +++ b/packages/app-project/public/locales/hi/screens.json @@ -41,7 +41,7 @@ "dismiss": "मुझे आगे से संदेश ना दिखाएं" }, "YourStats": { - "title": "{{projectName}} के आँकड़े" + "title": "आँकड़े" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/hr/screens.json b/packages/app-project/public/locales/hr/screens.json index 9e3c5b0848..1fedab0846 100644 --- a/packages/app-project/public/locales/hr/screens.json +++ b/packages/app-project/public/locales/hr/screens.json @@ -39,7 +39,7 @@ "dismiss": "Ne pokazuj mi ove poruke u budućnosti." }, "YourStats": { - "title": "{{projectName}} statistika" + "title": "statistika" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/it/screens.json b/packages/app-project/public/locales/it/screens.json index 6d014d5616..125e11c0a2 100644 --- a/packages/app-project/public/locales/it/screens.json +++ b/packages/app-project/public/locales/it/screens.json @@ -31,7 +31,7 @@ "dismiss": "Non mostrare più questi messaggi." }, "YourStats": { - "title": "Statistiche {{projectName}}" + "title": "Statistiche" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/ja/screens.json b/packages/app-project/public/locales/ja/screens.json index 89e2dd76d0..81fbc39a84 100644 --- a/packages/app-project/public/locales/ja/screens.json +++ b/packages/app-project/public/locales/ja/screens.json @@ -50,7 +50,7 @@ "dismiss": "今後メッセージを表示しない" }, "YourStats": { - "title": "{{projectName}} 統計" + "title": "統計" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/nl/screens.json b/packages/app-project/public/locales/nl/screens.json index cce6a48270..68bed59b20 100644 --- a/packages/app-project/public/locales/nl/screens.json +++ b/packages/app-project/public/locales/nl/screens.json @@ -31,7 +31,7 @@ "title": "Je recente waarnemingen" }, "YourStats": { - "title": "{{projectName}} statistieken" + "title": "statistieken" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/pl/screens.json b/packages/app-project/public/locales/pl/screens.json index cbbfb1722b..2ecdb6e6fb 100644 --- a/packages/app-project/public/locales/pl/screens.json +++ b/packages/app-project/public/locales/pl/screens.json @@ -24,7 +24,7 @@ }, "Classify": { "YourStats": { - "title": "{{projectName}} statystyka" + "title": "statystyka" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/pt/screens.json b/packages/app-project/public/locales/pt/screens.json index 4b09d20783..479b44a7b1 100644 --- a/packages/app-project/public/locales/pt/screens.json +++ b/packages/app-project/public/locales/pt/screens.json @@ -42,13 +42,7 @@ "title": "Novo fluxo de trabalho disponível" }, "YourStats": { - "DailyClassificationsChart": { - "title": "Número de classificações diárias de {{projectName}}" - }, - "text": "Continue o bom trabalho!", - "title": "Estatísticas {{projectName}}", - "todaysCount": "Classificações de hoje", - "totalCount": "Total de classificações" + "title": "Estatísticas" } }, "Home": { diff --git a/packages/app-project/public/locales/ru/screens.json b/packages/app-project/public/locales/ru/screens.json index c01fb99f8d..690c5e245b 100644 --- a/packages/app-project/public/locales/ru/screens.json +++ b/packages/app-project/public/locales/ru/screens.json @@ -39,7 +39,7 @@ "dismiss": "Не показывать больше" }, "YourStats": { - "title": "{{projectName}} Статистика" + "title": "Статистика" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/sv/screens.json b/packages/app-project/public/locales/sv/screens.json index 97d3a8a318..5c0b1fc521 100644 --- a/packages/app-project/public/locales/sv/screens.json +++ b/packages/app-project/public/locales/sv/screens.json @@ -40,7 +40,7 @@ "dismiss": "Visa inte fler meddelanden." }, "YourStats": { - "title": "{{projectName}} statistik" + "title": "statistik" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/test/screens.json b/packages/app-project/public/locales/test/screens.json index 2f543a2b4e..2166d7790d 100644 --- a/packages/app-project/public/locales/test/screens.json +++ b/packages/app-project/public/locales/test/screens.json @@ -42,13 +42,7 @@ "title": "Translated" }, "YourStats": { - "DailyClassificationsChart": { - "title": "{{projectName}} Translated" - }, - "text": "Translated", - "title": "Translated {{projectName}} Translated", - "todaysCount": "Translated", - "totalCount": "Translated" + "title": "Translated" } }, "Home": { diff --git a/packages/app-project/public/locales/tr/screens.json b/packages/app-project/public/locales/tr/screens.json index be37e7b6a2..e1e1bc4f93 100644 --- a/packages/app-project/public/locales/tr/screens.json +++ b/packages/app-project/public/locales/tr/screens.json @@ -42,13 +42,7 @@ "title": "Yeni İş Akışı Mevcut" }, "YourStats": { - "DailyClassificationsChart": { - "title": "{{projectName}} günlük sınıflandırma sayıları" - }, - "text": "Böyle harika çalışmaya devam edin!", - "title": "{{projectName}} İstatistiğiniz", - "todaysCount": "Bugünün sınıflandırmaları", - "totalCount": "Toplam sınıflandırma" + "title": "İstatistiğiniz" } }, "Home": { diff --git a/packages/app-project/public/locales/zh-CN/screens.json b/packages/app-project/public/locales/zh-CN/screens.json index cd19b8ff94..fa294385a6 100644 --- a/packages/app-project/public/locales/zh-CN/screens.json +++ b/packages/app-project/public/locales/zh-CN/screens.json @@ -27,7 +27,7 @@ }, "Classify": { "YourStats": { - "title": "{{projectName}} 数据" + "title": "数据" } } -} \ No newline at end of file +} diff --git a/packages/app-project/public/locales/zh-TW/screens.json b/packages/app-project/public/locales/zh-TW/screens.json index 1c8a845483..687d6d5368 100644 --- a/packages/app-project/public/locales/zh-TW/screens.json +++ b/packages/app-project/public/locales/zh-TW/screens.json @@ -30,7 +30,7 @@ "title": "這些是你最近完成的分類" }, "YourStats": { - "title": "{{projectName}} 數據" + "title": "數據" } } -} \ No newline at end of file +} diff --git a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js index dfbb5784a3..3a9f958f51 100644 --- a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js +++ b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js @@ -110,7 +110,7 @@ function ClassifyPage({ diff --git a/packages/app-project/src/screens/ClassifyPage/components/RecentSubjects/RecentSubjects.js b/packages/app-project/src/screens/ClassifyPage/components/RecentSubjects/RecentSubjects.js index fff6ec3c30..0727a0b18d 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/RecentSubjects/RecentSubjects.js +++ b/packages/app-project/src/screens/ClassifyPage/components/RecentSubjects/RecentSubjects.js @@ -43,9 +43,9 @@ function RecentSubjects({ return ( - + {/* {t('Classify.RecentSubjects.text')} - + */} + {label} + + {/* Insert commmas where appropriate */} + {value.toLocaleString()} + + + ) +} const defaultStatsData = { allTimeStats: { @@ -36,6 +53,8 @@ function YourProjectStats({ userID = '', userLogin = '' }) { + const { t } = useTranslation('screens') + const size = useContext(ResponsiveContext) const linkProps = { externalLink: true, href: `https://www.zooniverse.org/users/${userLogin}/stats?project_id=${projectID}` @@ -43,8 +62,8 @@ function YourProjectStats({ return ( {userID ? ( @@ -55,29 +74,24 @@ function YourProjectStats({ ) : error ? ( - There was an error loading your stats. + {error.message} ) : data ? ( - - - - - - + + + ) : null} diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js index ef56684b96..55d490648e 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.stories.js @@ -1,34 +1,10 @@ import YourProjectStats from './YourProjectStats' -import { - getTodayDateString, - getNumDaysAgoDateString -} from './helpers/dateRangeHelpers' - -// Mock stats data of a user who classified on three days in the past seven days -const sevenDaysAgoString = getNumDaysAgoDateString(6) -const threeDaysAgoString = getNumDaysAgoDateString(2) -const todayDateString = getTodayDateString() - const mockData = { allTimeStats: { total_count: 9436 }, sevenDaysStats: { - data: [ - { - count: 5, - period: sevenDaysAgoString - }, - { - count: 23, - period: threeDaysAgoString - }, - { - count: 12, - period: todayDateString - } - ], total_count: 40 } } diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChart.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChart.js deleted file mode 100644 index 72398b1a70..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChart.js +++ /dev/null @@ -1,140 +0,0 @@ -import { arrayOf, string, shape, number } from 'prop-types' -import styled, { css, useTheme } from 'styled-components' -import { AxisBottom, AxisLeft } from '@visx/axis' -import { Group } from '@visx/group' -import { Bar } from '@visx/shape' -import { Text } from '@visx/text' -import { scaleBand, scaleLinear } from '@visx/scale' - -const StyledBarGroup = styled(Group)` - &:hover rect, - &:focus rect { - ${props => - css` - fill: ${props.theme.global.colors.brand}; - `} - } - - &:hover text, - &:focus text { - display: block; - } -` - -const StyledBarLabel = styled(Text)` - display: none; // hide until bar is hovered or focused - - ${props => css` - fill: ${props.theme.global.colors['neutral-1']}; - font-size: 1rem; - `} -` - -function ClassificationsChart({ stats = [] }) { - const theme = useTheme() - - const HEIGHT = 300 - const PADDING = 25 - const WIDTH = 500 - const xScale = scaleBand({ - range: [0, WIDTH], - round: true, - domain: stats.map(stat => stat.longLabel), - padding: 0.1 - }) - - const yScale = scaleLinear({ - range: [HEIGHT - PADDING, 0], - round: true, - domain: [0, Math.max(...stats.map(stat => stat.count), 10)], - nice: true - }) - - const axisColour = theme.dark - ? theme.global.colors.white - : theme.global.colors.black - - const tickLabelProps = { - 'aria-hidden': 'true', - dx: '-0.25em', - dy: '0.2em', - fill: axisColour, - fontFamily: theme.global.font.family, - fontSize: '1rem', - textAnchor: 'middle' - } - - function shortDayLabels(dayName) { - const stat = stats.find(stat => stat.longLabel === dayName) - return stat.label - } - - return ( -
- - - - {stats.map(stat => { - const barWidth = xScale.bandwidth() - const barHeight = HEIGHT - PADDING - yScale(stat.count) - const barX = xScale(stat.longLabel) - const barY = HEIGHT - PADDING - barHeight - return ( - - - - - ) - })} - - - -
- ) -} - -ClassificationsChart.propTypes = { - stats: arrayOf( - shape({ - alt: string, - count: number, - label: string, - longLabel: string, - period: string - }) - ) -} - -export default ClassificationsChart diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChartContainer.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChartContainer.js deleted file mode 100644 index e3a1553008..0000000000 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/components/ClassificationsChartContainer.js +++ /dev/null @@ -1,70 +0,0 @@ -import ClassificationsChart from './ClassificationsChart.js' -import { arrayOf, number, shape, string } from 'prop-types' -import { useRouter } from 'next/router.js' - -import { getTodayDateString, getNumDaysAgoDateString } from '../helpers/dateRangeHelpers.js' - -const defaultStatsData = { - data: [{ - count: 0, - period: '' - }], - total_count: 0 -} - -export default function ClassificationsChartContainer({ stats = defaultStatsData }) { - const router = useRouter() - - // Similar to getCompleteData() in lib-user's bar chart. - // The data.period array returned from ERAS includes only days you've classified on - // but we want to display all seven days in the ClassificationsChart - const completeData = [] - - // Use the same date strings that were used in the sevenDaysAgo ERAS query - const todayDateString = getTodayDateString() - const sevenDaysAgoString = getNumDaysAgoDateString(6) - - // Loop to current date starting seven days ago and see if there's matching data returned from ERAS - let currentDate = new Date(sevenDaysAgoString) - const endDate = new Date(todayDateString) - - while (currentDate <= endDate) { - const matchingData = stats.data.find(stat => { - const statPeriod = new Date(stat.period) - const match = currentDate.getUTCDate() === statPeriod.getUTCDate() - return match - }) - - if (matchingData) { - completeData.push(matchingData) - } else { - completeData.push({ - count: 0, - period: currentDate.toISOString().substring(0, 10) - }) - } - - currentDate.setUTCDate(currentDate.getUTCDate() + 1) - } - - // Attach 'day of the week' labels to each stat - const statsWithLabels = completeData.map(({ count, period }) => { - const date = new Date(period) - const longLabel = date.toLocaleDateString(router?.locale, { timeZone: 'UTC', weekday: 'long' }) - const alt = `${longLabel}: ${count}` - const label = date.toLocaleDateString(router?.locale, { timeZone: 'UTC', weekday: 'short' }) - return { alt, count, label, longLabel, period } - }) - - return -} - -ClassificationsChartContainer.propTypes = { - stats: shape({ - data: arrayOf(shape({ - count: number, - period: string - })), - total_count: number - }) -} diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js index da445eca3f..0eb46da322 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js @@ -91,7 +91,7 @@ async function fetchStats({ endpoint, projectID, userID, authorization }) { allTimeStats } } catch (error) { - console.error('Error fetching stats', error) + console.error(error) logToSentry(error) } return null From b8720b713559150a030cb38d34ae22ed4edc74f9 Mon Sep 17 00:00:00 2001 From: Delilah <23665803+goplayoutside3@users.noreply.github.com> Date: Tue, 19 Nov 2024 20:07:25 -0600 Subject: [PATCH 10/16] write unit tests for YourProjectStats --- .../ClassifierWrapper.spec.js | 83 +------------------ .../YourProjectStats/YourProjectStats.js | 4 +- .../YourProjectStats/YourProjectStats.spec.js | 60 ++++++++++++++ 3 files changed, 64 insertions(+), 83 deletions(-) diff --git a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.spec.js b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.spec.js index 3583443ad5..5d934be96a 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.spec.js +++ b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.spec.js @@ -62,7 +62,6 @@ describe('Component > ClassifierWrapper', function () { describe('with a project, user, user project preferences loaded', function () { let recents let collections - let yourStats let wrapper before(function () { @@ -79,9 +78,7 @@ describe('Component > ClassifierWrapper', function () { addFavourites: sinon.stub(), removeFavourites: sinon.stub() } - yourStats = { - increment: sinon.stub() - } + const user = { loadingState: asyncStates.success } @@ -93,7 +90,6 @@ describe('Component > ClassifierWrapper', function () { recents={recents} router={router} user={user} - yourStats={yourStats} />, { wrappingComponent: TestWrapper } ).find(Classifier) @@ -104,82 +100,12 @@ describe('Component > ClassifierWrapper', function () { }) describe('on classification complete', function () { - describe('with a new subject', function () { - before(function () { - const subject = { - id: '1', - already_seen: false, - favorite: false, - retired: false, - locations: [ - { 'image/jpeg': 'thing.jpg' } - ] - } - wrapper.props().onCompleteClassification({}, subject) - }) - - after(function () { - yourStats.increment.resetHistory() - recents.add.resetHistory() - }) - - it('should increment stats', function () { - expect(yourStats.increment).to.have.been.calledOnce() - }) - - it('should add to recents', function () { - const recent = { - favorite: false, - subjectId: '1', - locations: [ - { 'image/jpeg': 'thing.jpg' } - ] - } - expect(recents.add.withArgs(recent)).to.have.been.calledOnce() - }) - }) - - describe('with a retired subject', function () { + describe('with user signed in and any subject', function () { before(function () { const subject = { id: '1', already_seen: false, favorite: false, - retired: true, - locations: [ - { 'image/jpeg': 'thing.jpg' } - ] - } - wrapper.props().onCompleteClassification({}, subject) - }) - - after(function () { - yourStats.increment.resetHistory() - recents.add.resetHistory() - }) - - it('should not increment stats', function () { - expect(yourStats.increment).to.not.have.been.called() - }) - - it('should add to recents', function () { - const recent = { - favorite: false, - subjectId: '1', - locations: [ - { 'image/jpeg': 'thing.jpg' } - ] - } - expect(recents.add.withArgs(recent)).to.have.been.calledOnce() - }) - }) - - describe('with a seen subject', function () { - before(function () { - const subject = { - id: '1', - already_seen: true, - favorite: false, retired: false, locations: [ { 'image/jpeg': 'thing.jpg' } @@ -189,14 +115,9 @@ describe('Component > ClassifierWrapper', function () { }) after(function () { - yourStats.increment.resetHistory() recents.add.resetHistory() }) - it('should not increment stats', function () { - expect(yourStats.increment).to.not.have.been.called() - }) - it('should add to recents', function () { const recent = { favorite: false, diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js index bc945a3d49..1a08b08e69 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js @@ -63,8 +63,8 @@ function YourProjectStats({ return ( {userID ? ( <> diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.spec.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.spec.js index e69de29bb2..d2768e703d 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.spec.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.spec.js @@ -0,0 +1,60 @@ +import { render, screen } from '@testing-library/react' +import { composeStory } from '@storybook/react' +import Meta, { NoUser, WithUser, Error } from './YourProjectStats.stories.js' + +const NoUserStory = composeStory(NoUser, Meta) +const WithUserStory = composeStory(WithUser, Meta) +const ErrorStory = composeStory(Error, Meta) + +describe('Component > YourProjectStats', function() { + describe('without a signed in user', function() { + beforeEach(function() { + render() + }) + + it('should show the RequireUser message', function() { + expect(screen.getByText('RequireUser.text')).to.be.ok() + }) + + it('should not have a link to user /stats page', function() { + expect(screen.queryByText('Classify.YourStats.link')).to.be.null() + }) + }) + + describe('with a signed in user', function() { + beforeEach(function() { + render() + }) + + it('should have a link to the user /stats page', function() { + expect(screen.getByText('Classify.YourStats.link')).to.be.ok() + }) + + it('should have a link with href to user /stats page filtered to the current project', function() { + const link = document.querySelector(`a[href='https://www.zooniverse.org/users/zootester1/stats?project_id=1234']`) + expect(link).to.be.ok() + }) + + it('should show user stats data', function() { + const sevenDaysStatLabel = screen.getByText('Classify.YourStats.lastSeven') + expect(sevenDaysStatLabel.nextElementSibling.innerHTML).to.equal('40') + + const allTimeStatLabel = screen.getByText('Classify.YourStats.allTime') + expect(allTimeStatLabel.nextElementSibling.innerHTML).to.equal('9,436') + }) + }) + + describe('when fetching stats throws an error', function() { + before(function() { + render() + }) + + it('should display the error message', function() { + expect(screen.getByText('There was an error fetching your stats')).to.be.ok() + }) + + it('should not have a link to user /stats page', function() { + expect(screen.queryByText('Classify.YourStats.link')).to.be.null() + }) + }) +}) From 7705b3f145ad41f508339af93292887b409dacf4 Mon Sep 17 00:00:00 2001 From: Delilah <23665803+goplayoutside3@users.noreply.github.com> Date: Tue, 19 Nov 2024 20:36:15 -0600 Subject: [PATCH 11/16] include mutate in the ClassifierWrapper useCallback --- .../components/ClassifierWrapper/ClassifierWrapper.js | 2 +- .../UserHome/components/Dashboard/DashboardContainer.js | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js index 3029093813..e32701fbec 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js +++ b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js @@ -69,7 +69,7 @@ export default function ClassifierWrapper({ subjectId: subject.id, locations: subject.locations }) - }, [addRecents, projectID, userID]) + }, [addRecents, mutate, projectID, userID]) /* If the page URL contains a subject ID, update that ID when the classification subject changes. diff --git a/packages/lib-user/src/components/UserHome/components/Dashboard/DashboardContainer.js b/packages/lib-user/src/components/UserHome/components/Dashboard/DashboardContainer.js index bc2fe539cd..1006e74ffe 100644 --- a/packages/lib-user/src/components/UserHome/components/Dashboard/DashboardContainer.js +++ b/packages/lib-user/src/components/UserHome/components/Dashboard/DashboardContainer.js @@ -33,7 +33,6 @@ const fetchProfileBanner = async ({ authUser }) => { export default function DashboardContainer({ authUser }) { const key = { endpoint: '/users/[id]', authUser } const { data: user, isLoading: userLoading } = useSWR(key, fetchProfileBanner, SWROptions) - console.log('lib-user', user) return ( Date: Wed, 20 Nov 2024 11:17:27 -0600 Subject: [PATCH 12/16] be sure to increment session count on each classification --- .../ClassifierWrapper/ClassifierWrapper.js | 7 +++-- .../ClassifierWrapper.spec.js | 11 ++++++++ .../ClassifierWrapperConnector.js | 3 +++ packages/app-project/stores/Store.spec.js | 2 +- .../UserPersonalization.js | 2 +- .../UserPersonalization.spec.js | 26 +++++++++---------- 6 files changed, 34 insertions(+), 17 deletions(-) diff --git a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js index e32701fbec..7e2fe25172 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js +++ b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js @@ -37,6 +37,7 @@ export default function ClassifierWrapper({ mode, onAddToCollection = DEFAULT_HANDLER, onSubjectReset = DEFAULT_HANDLER, + personalization = null, project = null, recents = null, router = null, @@ -55,14 +56,16 @@ export default function ClassifierWrapper({ const projectSlug = router?.query.project /* - Increment user stats on every classification submitted. - Add the recently classified subject to the user's Recents. + Increment sessionCount regardless if a user is signed-in (for auth invitation UI). + Increment signed-in user stats on every classification submitted. + Add the recently classified subject to the signed-in user's Recents. */ const projectID = project?.id const { mutate } = useYourProjectStats({ projectID, userID }) const addRecents = recents?.add const onCompleteClassification = useCallback((classification, subject) => { + personalization.incrementSessionCount() incrementStats(mutate, projectID, userID) addRecents({ favorite: subject.favorite, diff --git a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.spec.js b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.spec.js index 5d934be96a..0aeda58e73 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.spec.js +++ b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.spec.js @@ -62,6 +62,7 @@ describe('Component > ClassifierWrapper', function () { describe('with a project, user, user project preferences loaded', function () { let recents let collections + let personalization let wrapper before(function () { @@ -79,6 +80,10 @@ describe('Component > ClassifierWrapper', function () { removeFavourites: sinon.stub() } + personalization = { + incrementSessionCount: sinon.stub() + } + const user = { loadingState: asyncStates.success } @@ -86,6 +91,7 @@ describe('Component > ClassifierWrapper', function () { ClassifierWrapper', function () { after(function () { recents.add.resetHistory() + personalization.incrementSessionCount.resetHistory() + }) + + it('should increment sessoin count', function () { + expect(personalization.incrementSessionCount).to.have.been.calledOnce() }) it('should add to recents', function () { diff --git a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapperConnector.js b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapperConnector.js index fc0309bebb..3de3ce4a83 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapperConnector.js +++ b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapperConnector.js @@ -20,6 +20,7 @@ function useStore(mockStore) { appLoadingState, collections : user.collections, mode, + personalization: user.personalization, project, recents: user.recents, user @@ -47,6 +48,7 @@ function ClassifierWrapperConnector({ mockStore, ...props }) { appLoadingState, collections, mode, + personalization, project, recents, user, @@ -57,6 +59,7 @@ function ClassifierWrapperConnector({ mockStore, ...props }) { appLoadingState={appLoadingState} collections={collections} mode={mode} + personalization={personalization} // We use a POJO here, as the `project` resource is also stored in a // `mobx-state-tree` store in the classifier and an MST node can't be in two // stores at the same time. diff --git a/packages/app-project/stores/Store.spec.js b/packages/app-project/stores/Store.spec.js index 7cff976675..fc3b5f77d3 100644 --- a/packages/app-project/stores/Store.spec.js +++ b/packages/app-project/stores/Store.spec.js @@ -79,7 +79,7 @@ describe('Stores > Store', function () { }, placeholderEnv) // We call increment so we have a session count // session count is volatile state that can't be set by snapshot - store.user.personalization.increment() + store.user.personalization.incrementSessionCount() const signedInUserAndPersonalization = store.user.toJSON() expect(store.user.personalization.sessionCount).to.equal(1) store.user.clear() diff --git a/packages/app-project/stores/User/UserPersonalization/UserPersonalization.js b/packages/app-project/stores/User/UserPersonalization/UserPersonalization.js index cc2da609fa..5e80d9e184 100644 --- a/packages/app-project/stores/User/UserPersonalization/UserPersonalization.js +++ b/packages/app-project/stores/User/UserPersonalization/UserPersonalization.js @@ -29,7 +29,7 @@ const UserPersonalization = types addDisposer(self, autorun(_onUserChange)) }, - increment() { + incrementSessionCount() { self.sessionCount = self.sessionCount + 1 const { user } = getRoot(self) if (user?.id && self.sessionCountIsDivisibleByFive) { diff --git a/packages/app-project/stores/User/UserPersonalization/UserPersonalization.spec.js b/packages/app-project/stores/User/UserPersonalization/UserPersonalization.spec.js index 8cc5701cb9..9200b950a5 100644 --- a/packages/app-project/stores/User/UserPersonalization/UserPersonalization.spec.js +++ b/packages/app-project/stores/User/UserPersonalization/UserPersonalization.spec.js @@ -110,7 +110,7 @@ describe('Stores > UserPersonalization', function () { login: 'test.user' } rootStore = initStore(true, { project, user }) - rootStore.user.personalization.increment() + rootStore.user.personalization.incrementSessionCount() }) it('should add 1 to your session count', function () { @@ -120,10 +120,10 @@ describe('Stores > UserPersonalization', function () { describe('every five classifications', function () { before(function () { rootStore.client.panoptes.get.resetHistory() - rootStore.user.personalization.increment() - rootStore.user.personalization.increment() - rootStore.user.personalization.increment() - rootStore.user.personalization.increment() + rootStore.user.personalization.incrementSessionCount() + rootStore.user.personalization.incrementSessionCount() + rootStore.user.personalization.incrementSessionCount() + rootStore.user.personalization.incrementSessionCount() }) it('should request for the user project preferences', function () { @@ -195,11 +195,11 @@ describe('Stores > UserPersonalization', function () { before(function () { rootStore.client.panoptes.get.resetHistory() rootStore.user.personalization.reset() - rootStore.user.personalization.increment() - rootStore.user.personalization.increment() - rootStore.user.personalization.increment() - rootStore.user.personalization.increment() - rootStore.user.personalization.increment() + rootStore.user.personalization.incrementSessionCount() + rootStore.user.personalization.incrementSessionCount() + rootStore.user.personalization.incrementSessionCount() + rootStore.user.personalization.incrementSessionCount() + rootStore.user.personalization.incrementSessionCount() }) it('should not trigger the child UPP store to request user preferences from Panoptes', function () { @@ -217,7 +217,7 @@ describe('Stores > UserPersonalization', function () { } sinon.stub(auth, 'checkBearerToken').callsFake(() => Promise.reject(new Error('Auth is not available'))) rootStore.user.set(user) - rootStore.user.personalization.increment() + rootStore.user.personalization.incrementSessionCount() }) after(function () { @@ -241,7 +241,7 @@ describe('Stores > UserPersonalization', function () { .query(true) .replyWithError('Panoptes is not available') rootStore.user.set(user) - rootStore.user.personalization.increment() + rootStore.user.personalization.incrementSessionCount() }) it('should count session classifications from 0', function () { @@ -269,7 +269,7 @@ describe('Stores > UserPersonalization', function () { settings: { workflow_id: '4444' } } }) - personalizationStore.increment() // increment to have a session count + personalizationStore.incrementSessionCount() // increment to have a session count const signedInUserPersonalization = personalizationStore.toJSON() expect(personalizationStore.sessionCount).to.equal(1) personalizationStore.reset() From 66ff7000ed59b25622ea5aae750a658d9484e65f Mon Sep 17 00:00:00 2001 From: Delilah <23665803+goplayoutside3@users.noreply.github.com> Date: Wed, 20 Nov 2024 11:52:39 -0600 Subject: [PATCH 13/16] add auth.checkCurrent() to usePanoptesAuth() hook --- packages/app-project/src/hooks/usePanoptesAuth.js | 3 ++- .../components/YourProjectStats/helpers/incrementStats.js | 1 + .../components/YourProjectStats/useYourProjectStats.js | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/app-project/src/hooks/usePanoptesAuth.js b/packages/app-project/src/hooks/usePanoptesAuth.js index ac752bbc25..61f4da9462 100644 --- a/packages/app-project/src/hooks/usePanoptesAuth.js +++ b/packages/app-project/src/hooks/usePanoptesAuth.js @@ -5,6 +5,7 @@ export default function usePanoptesAuth(userID) { const [authorization, setAuthorization] = useState() async function checkAuth() { + auth.checkCurrent() const token = await auth.checkBearerToken() const bearerToken = token ? `Bearer ${token}` : '' setAuthorization(bearerToken) @@ -15,4 +16,4 @@ export default function usePanoptesAuth(userID) { }, [userID]) return authorization -} \ No newline at end of file +} diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/incrementStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/incrementStats.js index ae434cc0f2..ad6eafec1f 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/incrementStats.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/helpers/incrementStats.js @@ -1,3 +1,4 @@ +/** mutate() is an SWR function passed from useYourProjectStats() via ClassifierWrapper */ export default function incrementStats(mutate, projectID, userID) { if (!projectID || !userID) return mutate( diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js index 0eb46da322..a8babde005 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js @@ -100,7 +100,7 @@ async function fetchStats({ endpoint, projectID, userID, authorization }) { export default function useYourProjectStats({ projectID, userID }) { const authorization = usePanoptesAuth(userID) - // only fetch stats when a userID is available. Don't fetch if no user logged in. + // only fetch stats when a userID is available. Don't fetch if no user signed in. const key = authorization && userID ? { endpoint, projectID, userID, authorization } : null return useSWR(key, fetchStats, SWROptions) } From d7ec5b049467e801ca698ca64a7d41ae086b1d79 Mon Sep 17 00:00:00 2001 From: Delilah <23665803+goplayoutside3@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:03:10 -0600 Subject: [PATCH 14/16] adjust layout of ClassifyPage --- .../src/screens/ClassifyPage/ClassifyPage.js | 35 +++++++++++-------- .../ClassifierWrapper/ClassifierWrapper.js | 22 ++++++------ .../RecentSubjects/RecentSubjects.js | 6 +--- .../RecentSubjects/RecentSubjects.stories.js | 25 ------------- .../RecentSubjects/RecentSubjectsConnector.js | 4 +-- .../YourProjectStats/YourProjectStats.js | 24 ++++++++----- .../Classifier/ClassifierContainer.js | 2 +- 7 files changed, 51 insertions(+), 67 deletions(-) diff --git a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js index 3a9f958f51..0f14156ea9 100644 --- a/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js +++ b/packages/app-project/src/screens/ClassifyPage/ClassifyPage.js @@ -1,8 +1,8 @@ -import { Box, Grid } from 'grommet' +import { Box, Grid, ResponsiveContext } from 'grommet' import dynamic from 'next/dynamic' import { arrayOf, func, object, shape, string } from 'prop-types' -import { useCallback, useState } from 'react' -import withResponsiveContext from '@zooniverse/react-components/helpers/withResponsiveContext' +import { useCallback, useContext, useState } from 'react' +import styled from 'styled-components' import CollectionsModal from '@shared/components/CollectionsModal' import ConnectWithProject from '@shared/components/ConnectWithProject' @@ -18,16 +18,28 @@ export const ClassifierWrapper = dynamic(() => import('./components/ClassifierWrapper'), { ssr: false } ) + // This is a stopgap until we can use container queries via styled-components v6 + // or the RecentSubjects components gets a total redesign into a horizontal scrolling region +const StatsAndRecentsGrid = styled(Grid)` + width: 100%; + grid-template-columns: minmax(auto, 280px) auto; + + @media (width < 1000px) { + grid-template-columns: auto; + } +` + function ClassifyPage({ appLoadingState, onSubjectReset, - screenSize, subjectID, subjectSetID, workflowFromUrl, workflowID, workflows = [], }) { + const size = useContext(ResponsiveContext) + /* Enable session caching in the classifier for projects with ordered subject selection. */ @@ -88,7 +100,7 @@ function ClassifyPage({ gap='medium' pad='medium' > - + {!canClassify && appLoadingState === asyncStates.success && ( - + - - + + @@ -128,8 +137,6 @@ function ClassifyPage({ ClassifyPage.propTypes = { /** Sets subjectID state in ClassifyPageContainer to undefined */ onSubjectReset: func, - /** withResponsiveContext */ - screenSize: string, /** This subjectID is a state variable in ClassifyPageContainer */ subjectID: string, /** This subjectSetID is from getDefaultPageProps in page index.js */ @@ -146,5 +153,5 @@ ClassifyPage.propTypes = { })) } -export default withResponsiveContext(ClassifyPage) +export default ClassifyPage export { ClassifyPage } diff --git a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js index 7e2fe25172..9a3cebe03f 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js +++ b/packages/app-project/src/screens/ClassifyPage/components/ClassifierWrapper/ClassifierWrapper.js @@ -108,18 +108,24 @@ export default function ClassifierWrapper({ return isFavourite ? addFavourites([subjectId]) : removeFavourites([subjectId]) }, [addFavourites, removeFavourites]) - const somethingWentWrong = appLoadingState === asyncStates.error + /* Loading UI if user and project are loading in the store */ + if (appLoadingState === asyncStates.loading) + return ( + + + + ) - if (somethingWentWrong) { + /* Error UI if any errors loading the user or project in the store */ + if (appLoadingState === asyncStates.error) { const { error: projectError } = project const { error: userError } = user const errorToMessage = projectError || userError || new Error('Something went wrong') - return ( - - ) + return } + /* Display the Classifier */ try { if (appLoadingState === asyncStates.success) { const key = userID || 'no-user' @@ -149,12 +155,6 @@ export default function ClassifierWrapper({ onError(error) return } - - return ( - - - - ) } ClassifierWrapper.propTypes = { diff --git a/packages/app-project/src/screens/ClassifyPage/components/RecentSubjects/RecentSubjects.js b/packages/app-project/src/screens/ClassifyPage/components/RecentSubjects/RecentSubjects.js index 0727a0b18d..95160e67c2 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/RecentSubjects/RecentSubjects.js +++ b/packages/app-project/src/screens/ClassifyPage/components/RecentSubjects/RecentSubjects.js @@ -32,7 +32,6 @@ function Placeholder({ height }) { function RecentSubjects({ isLoggedIn = false, recents = [], - projectName, size = 3, slug }) { @@ -42,10 +41,7 @@ function RecentSubjects({ const placeholders = [...Array(size - displayedRecents.length)] return ( - - {/* - {t('Classify.RecentSubjects.text')} - */} + @@ -48,7 +30,6 @@ export function Placeholder({ isLoggedIn }) { @@ -61,7 +42,6 @@ export function NarrowScreens({ isLoggedIn }) { @@ -81,7 +61,6 @@ export function Transcription({ isLoggedIn }) { @@ -94,7 +73,6 @@ export function Video({ isLoggedIn }) { @@ -107,7 +85,6 @@ export function Data({ isLoggedIn }) { @@ -120,7 +97,6 @@ export function Text({ isLoggedIn }) { @@ -133,7 +109,6 @@ export function OneSubject({ isLoggedIn }) { diff --git a/packages/app-project/src/screens/ClassifyPage/components/RecentSubjects/RecentSubjectsConnector.js b/packages/app-project/src/screens/ClassifyPage/components/RecentSubjects/RecentSubjectsConnector.js index d93fa58e8d..368e65ddab 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/RecentSubjects/RecentSubjectsConnector.js +++ b/packages/app-project/src/screens/ClassifyPage/components/RecentSubjects/RecentSubjectsConnector.js @@ -18,19 +18,17 @@ function storeMapper (store) { return { isLoggedIn, recents, - projectName: project['display_name'], slug: project.slug } } function RecentSubjectsConnector({ size }) { const { store } = useContext(MobXProviderContext) - const { isLoggedIn, recents, projectName, slug } = storeMapper(store) + const { isLoggedIn, recents, slug } = storeMapper(store) return ( diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js index 1a08b08e69..362089e3e9 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/YourProjectStats.js @@ -1,13 +1,25 @@ -import { Box, ResponsiveContext, Text } from 'grommet' +import { Box, Text } from 'grommet' import Loader from '@zooniverse/react-components/Loader' import SpacedText from '@zooniverse/react-components/SpacedText' import { arrayOf, bool, object, number, shape, string } from 'prop-types' -import { useContext } from 'react' +import styled from 'styled-components' import ContentBox from '@shared/components/ContentBox' import RequireUser from '@shared/components/RequireUser/RequireUser.js' import { useTranslation } from 'next-i18next' +// See the same breakpoint in ClassifyPage.js +const StyledBox = styled(Box)` + flex-direction: column; + justify-content: center; + gap: 30px; + + @media (width < 1000px) { + flex-direction: row; + justify-content: center; + } +` + function Stat({ label = '', value = 0 }) { return ( @@ -54,7 +66,6 @@ function YourProjectStats({ userLogin = '' }) { const { t } = useTranslation('screens') - const size = useContext(ResponsiveContext) const linkProps = { externalLink: true, href: `https://www.zooniverse.org/users/${userLogin}/stats?project_id=${projectID}` @@ -77,12 +88,9 @@ function YourProjectStats({ {error.message} ) : data ? ( - - + ) : null} ) : ( diff --git a/packages/lib-classifier/src/components/Classifier/ClassifierContainer.js b/packages/lib-classifier/src/components/Classifier/ClassifierContainer.js index 7bada0c62b..e6044bc230 100644 --- a/packages/lib-classifier/src/components/Classifier/ClassifierContainer.js +++ b/packages/lib-classifier/src/components/Classifier/ClassifierContainer.js @@ -250,7 +250,7 @@ export default function ClassifierContainer({ subjectID={subjectID} workflowSnapshot={workflowSnapshot} /> : - Loading… + Loading the Classifier } ) From 5624eb5303886051f93d9cb2ef93b194118356e1 Mon Sep 17 00:00:00 2001 From: Delilah <23665803+goplayoutside3@users.noreply.github.com> Date: Thu, 21 Nov 2024 13:11:50 -0600 Subject: [PATCH 15/16] clean up translation keys --- packages/app-project/public/locales/en/screens.json | 1 - packages/app-project/public/locales/es/screens.json | 1 - packages/app-project/public/locales/fr/screens.json | 1 - packages/app-project/public/locales/pt/screens.json | 1 - packages/app-project/public/locales/test/screens.json | 1 - packages/app-project/public/locales/tr/screens.json | 1 - 6 files changed, 6 deletions(-) diff --git a/packages/app-project/public/locales/en/screens.json b/packages/app-project/public/locales/en/screens.json index 168fe9cd2d..c59f784e4b 100644 --- a/packages/app-project/public/locales/en/screens.json +++ b/packages/app-project/public/locales/en/screens.json @@ -56,7 +56,6 @@ } }, "RecentSubjects": { - "text": "Discuss a subject on Talk, or add it to your Favourites or a collection.", "title": "Your recent classifications" }, "WorkflowAssignmentModal": { diff --git a/packages/app-project/public/locales/es/screens.json b/packages/app-project/public/locales/es/screens.json index f791069297..d6f34bc03c 100644 --- a/packages/app-project/public/locales/es/screens.json +++ b/packages/app-project/public/locales/es/screens.json @@ -31,7 +31,6 @@ } }, "RecentSubjects": { - "text": "Comenta un objeto en Hablemos o añádelo a tus Favoritos o a una colección.", "title": "Tus clasificaciones recientes" }, "WorkflowAssignmentModal": { diff --git a/packages/app-project/public/locales/fr/screens.json b/packages/app-project/public/locales/fr/screens.json index 7b3468b9d8..c1ae6a66ef 100644 --- a/packages/app-project/public/locales/fr/screens.json +++ b/packages/app-project/public/locales/fr/screens.json @@ -31,7 +31,6 @@ } }, "RecentSubjects": { - "text": "Discutez d'un sujet sur Talk ou ajoutez-le à vos favoris ou à une collection.", "title": "Vos classifications récentes" }, "WorkflowAssignmentModal": { diff --git a/packages/app-project/public/locales/pt/screens.json b/packages/app-project/public/locales/pt/screens.json index 479b44a7b1..5e941e6313 100644 --- a/packages/app-project/public/locales/pt/screens.json +++ b/packages/app-project/public/locales/pt/screens.json @@ -31,7 +31,6 @@ } }, "RecentSubjects": { - "text": "Fale sobre qualquer objeto em Conversar ou adicione-o aos favoritos ou a uma coleção.", "title": "As suas classificações recentes" }, "WorkflowAssignmentModal": { diff --git a/packages/app-project/public/locales/test/screens.json b/packages/app-project/public/locales/test/screens.json index 2166d7790d..0a0172b69f 100644 --- a/packages/app-project/public/locales/test/screens.json +++ b/packages/app-project/public/locales/test/screens.json @@ -31,7 +31,6 @@ } }, "RecentSubjects": { - "text": "Translated", "title": "Translated" }, "WorkflowAssignmentModal": { diff --git a/packages/app-project/public/locales/tr/screens.json b/packages/app-project/public/locales/tr/screens.json index e1e1bc4f93..b9cf5d883a 100644 --- a/packages/app-project/public/locales/tr/screens.json +++ b/packages/app-project/public/locales/tr/screens.json @@ -31,7 +31,6 @@ } }, "RecentSubjects": { - "text": "Sohbet'te bir konuyu tartışın veya Favorilerinize ya da bir koleksiyona ekleyin.", "title": "En son çalışmalarınız" }, "WorkflowAssignmentModal": { From 3f2b679e71a4f054aef2c1889dcf3785ed8eb814 Mon Sep 17 00:00:00 2001 From: Delilah <23665803+goplayoutside3@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:43:57 -0600 Subject: [PATCH 16/16] copy usePanoptesAuthToken hook behavior from lib-user into app-project --- packages/app-project/src/hooks/index.js | 1 + .../app-project/src/hooks/usePanoptesAuth.js | 2 ++ .../src/hooks/usePanoptesAuthToken.js | 36 +++++++++++++++++++ .../YourProjectStats/useYourProjectStats.js | 9 ++--- 4 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 packages/app-project/src/hooks/usePanoptesAuthToken.js diff --git a/packages/app-project/src/hooks/index.js b/packages/app-project/src/hooks/index.js index f1ea78820a..7797b35eeb 100644 --- a/packages/app-project/src/hooks/index.js +++ b/packages/app-project/src/hooks/index.js @@ -1,6 +1,7 @@ export { default as useAdminMode } from './useAdminMode.js' export { default as useUserFavourites } from './useUserFavourites.js' export { default as usePanoptesAuth } from './usePanoptesAuth.js' +export { default as usePanoptesAuthToken } from './usePanoptesAuthToken.js' export { default as usePanoptesUser } from './usePanoptesUser.js' export { default as usePreferredTheme } from './usePreferredTheme.js' export { default as useSugarProject } from './useSugarProject.js' diff --git a/packages/app-project/src/hooks/usePanoptesAuth.js b/packages/app-project/src/hooks/usePanoptesAuth.js index 61f4da9462..9658a5b53e 100644 --- a/packages/app-project/src/hooks/usePanoptesAuth.js +++ b/packages/app-project/src/hooks/usePanoptesAuth.js @@ -1,3 +1,5 @@ +/* This hook will be fully replaced with usePanoptesAuthToken. See PR 6472 for more info */ + import auth from 'panoptes-client/lib/auth' import { useEffect, useState } from 'react' diff --git a/packages/app-project/src/hooks/usePanoptesAuthToken.js b/packages/app-project/src/hooks/usePanoptesAuthToken.js new file mode 100644 index 0000000000..d28f079b5d --- /dev/null +++ b/packages/app-project/src/hooks/usePanoptesAuthToken.js @@ -0,0 +1,36 @@ +import auth from 'panoptes-client/lib/auth' +import { useState } from 'react' + +const isBrowser = typeof window !== 'undefined' + +let defaultToken +/* + See comments in https://github.com/zooniverse/front-end-monorepo/pull/6345 + Top-level await in modules has been supported in Node + and in all browsers since 2021. However, ES modules are still + not supported in the monorepo. An immediately-invoked async + function is a workaround when top-level await is not supported. + https://v8.dev/features/top-level-await +*/ +(async function getDefaultToken() { + defaultToken = null + if (isBrowser) { + await auth.checkCurrent() + defaultToken = await auth.checkBearerToken() + } +})() + +export default function usePanoptesAuthToken() { + const [token, setToken] = useState(defaultToken) + + async function fetchPanoptesAuthToken() { + await auth.checkCurrent() + const newToken = await auth.checkBearerToken() + if (newToken !== token) { + setToken(newToken) + } + } + + fetchPanoptesAuthToken() + return token +} diff --git a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js index a8babde005..20dcf2e7f3 100644 --- a/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js +++ b/packages/app-project/src/screens/ClassifyPage/components/YourProjectStats/useYourProjectStats.js @@ -3,7 +3,7 @@ import { env, panoptes } from '@zooniverse/panoptes-js' import getServerSideAPIHost from '@helpers/getServerSideAPIHost' import logToSentry from '@helpers/logger/logToSentry.js' -import { usePanoptesAuth } from '@hooks' +import { usePanoptesAuthToken } from '@hooks' import { getTodayDateString, getNumDaysAgoDateString, getQueryPeriod } from './helpers/dateRangeHelpers.js' const SWROptions = { @@ -70,7 +70,8 @@ function formatAllTimeStatsQuery(userCreatedAt) { return new URLSearchParams(query).toString() } -async function fetchStats({ endpoint, projectID, userID, authorization }) { +async function fetchStats({ endpoint, projectID, userID, token }) { + const authorization = `Bearer ${token}` const headers = { authorization } const host = statsHost(env) @@ -98,9 +99,9 @@ async function fetchStats({ endpoint, projectID, userID, authorization }) { } export default function useYourProjectStats({ projectID, userID }) { - const authorization = usePanoptesAuth(userID) + const token = usePanoptesAuthToken() // only fetch stats when a userID is available. Don't fetch if no user signed in. - const key = authorization && userID ? { endpoint, projectID, userID, authorization } : null + const key = token && userID ? { endpoint, projectID, userID, token } : null return useSWR(key, fetchStats, SWROptions) }