From 4d0cde2ce9e1f20efc0768ac1946ac917b24eea1 Mon Sep 17 00:00:00 2001 From: "Delilah C." <23665803+goplayoutside3@users.noreply.github.com> Date: Mon, 18 Nov 2024 10:01:46 -0600 Subject: [PATCH] ADR 61: Displaying Stats in UTC Date Ranges (#6442) * add ADR and x-axis label * add an FAQ question + answer * fix a typo * remove stray '-' in BarChart css * Fix a typo in the ADR Co-authored-by: Mark Bouslog * Fix a typo in the ADR Co-authored-by: Mark Bouslog --------- Co-authored-by: Mark Bouslog --- docs/arch/README.md | 1 + docs/arch/adr-61.md | 47 +++++++++++++++++++ packages/lib-content/src/screens/FAQ/FAQ.js | 37 +++++++++------ packages/lib-content/src/translations/en.json | 14 ++++-- .../components/shared/BarChart/BarChart.js | 25 ++++++++-- 5 files changed, 102 insertions(+), 22 deletions(-) create mode 100644 docs/arch/adr-61.md diff --git a/docs/arch/README.md b/docs/arch/README.md index 419ccf5e71..7470e6e863 100644 --- a/docs/arch/README.md +++ b/docs/arch/README.md @@ -60,3 +60,4 @@ - [ADR 58: Internationalisation with the Panoptes translations API](adr-58.md) - [ADR 59: Incremental Static Regeneration of project pages](adr-59.md) - [ADR 60: Enabling Aggregations (Caesar Reducers) on Workflows](adr-60.md) +- [ADR 61: Displaying Stats in UTC Date Ranges](adr-61.md) diff --git a/docs/arch/adr-61.md b/docs/arch/adr-61.md new file mode 100644 index 0000000000..b47869e549 --- /dev/null +++ b/docs/arch/adr-61.md @@ -0,0 +1,47 @@ +# Displaying Stats in UTC Date Ranges + +## Status +Accepted + + +## Context + +Launch of the new homepages, user stats pages, and group stats pages have put a spotlight on our stats service. The new features allow users to see their classification counts and time spent increase after every contribution to a project. + +Classification metadata is stored in the database with UTC timestamps, and the stats service, ERAS, aggregates classification counts into buckets by UTC date ranges. However, not all UI components have clear labels indicating a UTC data range, and volunteers could encounter three unintuitive (but expected) scenarios: + +### When a volunteer's local timezone is behind UTC + +Example: A volunteer submits classifications at 9pm Chicago, but it's 3am UTC. + +Classification metadata is stored in our database with UTC timestamps. If a volunteer's local timezone is Oct 31, but the UTC date is Nov 1 then their classifications are put into the time bucket for Nov 1. In a bar chart UI component that displays "Last 7 Days", the volunteer sees Oct 26 - Nov 1. As this volunteer submits classifications, their classification count will increase in the bar for Nov 1 (not their local time of 9pm Oct 31). + +### When a volunteer's local timezone is ahead of UTC + +Example: A volunteer submits classifications at 8am Korea, but it's 11pm UTC. + +If a volunteer's local timezone is Nov 2, but the UTC date is Nov 1, then their classifications are still put into the time bucket for Nov 1. In a bar chart UI component that displays "Last 7 Days", the volunteer sees Oct 26 - Nov 1. As this volunteer submits classifications, their classification count will increase in the bar for Nov 1 (not their local time of 8am Nov 2). This volunteer won't see a bar for Nov 2 on the chart. + +### UI Labels that are too relative + +The YourStats component in app-project is displayed on each project's Classify page. It has a label for a user's classification count "today". Classification counts "today" are fetched as a time bucket for the current date UTC. However, a volunteer could easily assume "today" means the date of their local timezone, and fall into the two scenarios above. + +The YourStats component also has a week-long bar chart with "daily" classification counts that also falls into the two scenarios above. + + +## Consequences + +Whenever classification stats are displayed on the Zooniverse website, we should be clear that the date range is UTC, not a user's local timezone. The YourStats component in app-project has the most potential for volunteers to report discrepancies as described above because of its focus on "today". + +Following this ADR, the decisions below will be implemented. When a user navigates the Zooniverse website, their classification counts will be fetched and displayed consistently in UTC date ranges. + + +## Decision + +1. An x-axis label "Date Range (UTC)" will be added to bar charts displaying user stats, group stats, and project stats. + +2. A question + answer will be added to the About Zooniverse > FAQ page explaining why stats data are displayed in a UTC date range. + +3. The YourStats component in app-project will be redesigned and refactored to match the "Last 7 Days" and "All Time" stats components on a user's personal stats page. The label "Last 7 Days" is more adaptable to volunteers located across the globe, yet granular enough to display incrementing classification counts "live". + +4. The eventual redesign and development of the Project Stats page will follow the above practice of a clear x-axis label and data fetching in UTC date ranges. diff --git a/packages/lib-content/src/screens/FAQ/FAQ.js b/packages/lib-content/src/screens/FAQ/FAQ.js index f7f492d135..45a9358cf8 100644 --- a/packages/lib-content/src/screens/FAQ/FAQ.js +++ b/packages/lib-content/src/screens/FAQ/FAQ.js @@ -93,15 +93,24 @@ function FAQPage() { ]} + components={[ + + ]} /> {t('FAQ.item4.question')} + {t('FAQ.item4.answer')} + + + {t('FAQ.item5.question')} - {t('FAQ.item5.question')} + {t('FAQ.item6.question')} - {t('FAQ.item6.question')} + {t('FAQ.item7.question')} - {t('FAQ.item7.question')} - {t('FAQ.item7.answer0')} + {t('FAQ.item8.question')} + {t('FAQ.item8.answer0')} @@ -177,10 +186,10 @@ function FAQPage() { - {t('FAQ.item8.question')} + {t('FAQ.item9.question')} @@ -189,9 +198,9 @@ function FAQPage() { - {t('FAQ.item9.question')} + {t('FAQ.item10.question')} - {t('FAQ.item9.answer')} + {t('FAQ.item10.answer')} diff --git a/packages/lib-content/src/translations/en.json b/packages/lib-content/src/translations/en.json index e1cd75971e..82123172ee 100644 --- a/packages/lib-content/src/translations/en.json +++ b/packages/lib-content/src/translations/en.json @@ -227,28 +227,32 @@ "question": "Can I download a volunteer Certificate? Can I use Zooniverse to fulfill Service Learning requirements?" }, "item4": { + "answer": "Classification metadata is stored in our database with UTC timestamps, and the stats service aggregates classification counts into buckets by UTC date ranges. All stats are displayed consistently in UTC date ranges.", + "question": "Why are stats displayed in the UTC timezone?" + }, + "item5": { "answer": "The Zooniverse takes very seriously the task of protecting our volunteer's personal information and classification data. Details about these efforts can be found on the <0>Zooniverse User Agreement and Privacy Policy page and the <1>Zooniverse Security page.", "question": "What does the Zooniverse do with my account information?" }, - "item5": { + "item6": { "answer": "You can post your suggestions for new features and report bugs via <0>Zooniverse Talk, through an Issue on the relevant <1>Github repository, or <2>contact us.", "question": "I have a feature request or found a bug; who should I talk to/how do I report it?" }, - "item6": { + "item7": { "answer": "The Zooniverse is a collaboration between institutions from the United Kingdom and the United States; all of our team are employed by one or the other of these partner institutions. Check out the <0>Zooniverse jobs page to find out more about employment opportunities within the Zooniverse.", "question": "Is the Zooniverse hiring?" }, - "item7": { + "item8": { "answer0": "The Zooniverse is a platform for people-powered research, not an entity, company, or non-profit. We are a multi-institutional collaboration.", "answer1": "The Adler Planetarium in Chicago, one of the host institutions for the Zooniverse team, is a 501(c)(3). Individuals or organizations that need to link explicitly to a 501(c)(3) for their volunteering efforts use the Adler Planetarium as the reference. Documentation of the Adler Planetarium's 501(c)(3) status is provided <0>here, and the Adler Planetarium EIN number is 36-6210902. When inputting the organization name, please use “Adler Planetarium - Zooniverse” if possible. If not possible, please use “Adler Planetarium” (which matches the EIN number). In all cases, you'll volunteer through Zooniverse.org.", "answer2": "For information on how to donate, please visit our <0>donation page.", "question": "Is the Zooniverse a non-profit organization?" }, - "item8": { + "item9": { "answer": "You can find more details on how to cite the Zooniverse in research publications using data derived from use of the Zooniverse Project Builder on our <0>Resources page.", "question": "I'm a project owner/research team member, how do I acknowledge the Zooniverse and the Project Builder Platform in my paper, talk abstract, etc.?" }, - "item9": { + "item10": { "answer": "We support major browsers up to the second to last version.", "question": "What browser version does Zooniverse support?" } diff --git a/packages/lib-user/src/components/shared/BarChart/BarChart.js b/packages/lib-user/src/components/shared/BarChart/BarChart.js index c73857c0e8..3dc6a8bcce 100644 --- a/packages/lib-user/src/components/shared/BarChart/BarChart.js +++ b/packages/lib-user/src/components/shared/BarChart/BarChart.js @@ -24,6 +24,24 @@ const StyledDataChart = styled(DataChart)` .hidden-period-label { display: none; } + + // The only way to get to the x-axis bounding div + &.styled-grommet-barchart > :first-child { + + // Align the x-axis visual label to the first date label + div:first-of-type > span { + position: relative; + + &::after { + content: 'Date range (UTC)'; + position: absolute; + top: calc(100% + 5px); + left: 0; + font-size: 0.75rem; + width: max-content; + } + } + } ` function BarChart({ @@ -34,17 +52,17 @@ function BarChart({ type = 'count' }) { const size = useContext(ResponsiveContext) - + // getDateInterval returns an object with a period property based on the date range, start_date, and end_date const dateInterval = getDateInterval(dateRange) // getCompleteData returns an array of objects with a period, count, and session_time property, // including any periods without stats with a count and session_time of 0 const completeData = getCompleteData({ data, dateInterval }) - + const dateRangeLabel = getDateRangeLabel(dateInterval) const typeLabel = TYPE_LABEL[type] - + // with no data set gradient as 'brand' let gradient = 'brand' // with data set gradient range based on data type (count or session_time) and max value of data type @@ -89,6 +107,7 @@ function BarChart({ return (