Skip to content

Commit

Permalink
Add shimmer loading skeleton on k5 dash
Browse files Browse the repository at this point in the history
Includes a loading skeleton for k5 cards, homeroom announcements,
and for assignments and announcements on course cards.

A few other notes:
 - The card data is cached in local storage, so the skeleton for
   the cards themselves will only appear briefly if that data
   exists
 - We cache the number of cards that a user has server-side, and
   send that number in an ENV variable so we can display the
   correct number of card skeletons, however we don't wait for the
   caching to complete before sending card data back, so the first
   time a user ever logs in, we may default to 5 skeletons
 - We always show 1 skeleton for announcements, regardless of
   actual number

closes LS-2070
flag = canvas_for_elementary

Test plan:
 - Enroll a teacher and student in a homeroom course with an
   announcement and a subject course with an announcement and
   assignment (or more than one)
 - As student, load the dashboard; expect to see shimmer for the
   homeroom announcement and 2 lines on the subject course card for
   the assignment info and announcement - this either disappears or
   is replaced by content
 - There should also be brief shimmer for the cards themselves
 - Repeat for teacher, except there will only be 1 line of shimmer
   since teachers don't see assignment info

Change-Id: I56bd02d19bff2e7f2906e3c248ee482a21d933b6
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/262255
Tested-by: Service Cloud Jenkins <[email protected]>
Reviewed-by: Jeff Largent <[email protected]>
QA-Review: Jeff Largent <[email protected]>
Product-Review: Peyton Craighill <[email protected]>
  • Loading branch information
JacksonHowe committed Apr 9, 2021
1 parent 7391f70 commit 93fb36e
Show file tree
Hide file tree
Showing 14 changed files with 435 additions and 92 deletions.
4 changes: 3 additions & 1 deletion app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -493,7 +493,8 @@ def user_dashboard
},
:STUDENT_PLANNER_ENABLED => planner_enabled?,
:STUDENT_PLANNER_COURSES => planner_enabled? && map_courses_for_menu(@current_user.courses_with_primary_enrollment),
:STUDENT_PLANNER_GROUPS => planner_enabled? && map_groups_for_planner(@current_user.current_groups)
:STUDENT_PLANNER_GROUPS => planner_enabled? && map_groups_for_planner(@current_user.current_groups),
:INITIAL_NUM_K5_CARDS => Rails.cache.read(['last_known_k5_cards_count', @current_user.global_id].cache_key) || 5
})

@announcements = AccountNotification.for_user_and_account(@current_user, @domain_root_account)
Expand Down Expand Up @@ -537,6 +538,7 @@ def dashboard_cards
else
Rails.cache.write(['last_known_dashboard_cards_count', @current_user.global_id].cache_key, dashboard_courses.count)
end
Rails.cache.write(['last_known_k5_cards_count', @current_user.global_id].cache_key, dashboard_courses.reject{|c| c[:isHomeroom]}.count)
render json: dashboard_courses
end

Expand Down
10 changes: 10 additions & 0 deletions app/stylesheets/bundles/k5_dashboard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,13 @@
}
}
}

// Shimmer animation for the LoadingSkeleton component
@keyframes shimmer {
0% {
background-position: 0 0;
}
100% {
background-position: -100% 0%;
}
}
26 changes: 26 additions & 0 deletions spec/controllers/users_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2520,6 +2520,32 @@ def get_grades!(grading_period_id: nil)
end
end
end

context "ENV.INITIAL_NUM_K5_CARDS" do
before :once do
course_with_student
end

before :each do
user_session @student
end

it "is set to cached count" do
enable_cache do
Rails.cache.write(['last_known_k5_cards_count', @student.global_id].cache_key, 3)
get 'user_dashboard'
expect(assigns[:js_env][:INITIAL_NUM_K5_CARDS]).to eq 3
Rails.cache.delete(['last_known_k5_cards_count', @student.global_id].cache_key)
end
end

it "is set to 5 if not cached" do
enable_cache do
get 'user_dashboard'
expect(assigns[:js_env][:INITIAL_NUM_K5_CARDS]).to eq 5
end
end
end
end

describe "#pandata_events_token" do
Expand Down
88 changes: 58 additions & 30 deletions ui/features/k5_dashboard/react/HomeroomAnnouncementsLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,43 +17,71 @@
*/

import React from 'react'
import {array} from 'prop-types'
import I18n from 'i18n!dashboard_layout_HomeroomAnnouncementsLayout'
import {array, bool} from 'prop-types'
import HomeroomAnnouncement from './HomeroomAnnouncement'
import EmptyHomeroomAnnouncement from './EmptyHomeroomAnnouncement'
import {View} from '@instructure/ui-view'
import LoadingSkeleton from '@canvas/k5/react/LoadingSkeleton'

export default function HomeroomAnnouncementsLayout({homeroomAnnouncements}) {
export default function HomeroomAnnouncementsLayout({homeroomAnnouncements, loading}) {
return (
<View>
{homeroomAnnouncements?.length > 0 &&
homeroomAnnouncements.map(homeroom => {
if (homeroom.announcement) {
return (
<View key={homeroom.courseId}>
<HomeroomAnnouncement
courseName={homeroom.courseName}
courseUrl={homeroom.courseUrl}
canEdit={homeroom.canEdit}
title={homeroom.announcement.title}
message={homeroom.announcement.message}
url={homeroom.announcement.url}
attachment={homeroom.announcement.attachment}
/>
</View>
)
} else if (homeroom.canEdit) {
return (
<View key={homeroom.courseId}>
<EmptyHomeroomAnnouncement {...homeroom} />
</View>
)
}
return null
})}
</View>
<>
{loading ? (
<>
<LoadingSkeleton
screenReaderLabel={I18n.t('Loading Homeroom Course Name')}
margin="small 0"
width="20em"
height="1.5em"
/>
<LoadingSkeleton
screenReaderLabel={I18n.t('Loading Homeroom Announcement Title')}
margin="small 0"
width="15em"
height="1.5em"
/>
<LoadingSkeleton
screenReaderLabel={I18n.t('Loading Homeroom Announcement Content')}
margin="small 0"
width="100%"
height="8em"
/>
</>
) : (
<View>
{homeroomAnnouncements?.length > 0 &&
homeroomAnnouncements.map(homeroom => {
if (homeroom.announcement) {
return (
<View key={homeroom.courseId}>
<HomeroomAnnouncement
courseName={homeroom.courseName}
courseUrl={homeroom.courseUrl}
canEdit={homeroom.canEdit}
title={homeroom.announcement.title}
message={homeroom.announcement.message}
url={homeroom.announcement.url}
attachment={homeroom.announcement.attachment}
/>
</View>
)
} else if (homeroom.canEdit) {
return (
<View key={homeroom.courseId}>
<EmptyHomeroomAnnouncement {...homeroom} />
</View>
)
}
return null
})}
</View>
)}
</>
)
}

HomeroomAnnouncementsLayout.propTypes = {
homeroomAnnouncements: array.isRequired
homeroomAnnouncements: array.isRequired,
loading: bool.isRequired
}
74 changes: 53 additions & 21 deletions ui/features/k5_dashboard/react/HomeroomPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,12 @@ import I18n from 'i18n!k5_dashboard'
import {Heading} from '@instructure/ui-heading'
import {View} from '@instructure/ui-view'

import K5DashboardCard from './K5DashboardCard'
import K5DashboardCard, {CARD_SIZE_PX} from './K5DashboardCard'
import {createDashboardCards} from '@canvas/dashboard-card'
import {fetchLatestAnnouncement} from '@canvas/k5/react/utils'
import HomeroomAnnouncementsLayout from './HomeroomAnnouncementsLayout'
import {showFlashError} from '@canvas/alerts/react/FlashAlert'
import LoadingSkeleton from '@canvas/k5/react/LoadingSkeleton'

export const fetchHomeroomAnnouncements = cards =>
Promise.all(
Expand Down Expand Up @@ -68,49 +70,79 @@ export const fetchHomeroomAnnouncements = cards =>

export const HomeroomPage = props => {
const {cards, requestTabChange, visible} = props
const [dashboardCards] = useState(() =>
createDashboardCards(
cards.filter(c => !c.isHomeroom),
K5DashboardCard,
{
const [dashboardCards, setDashboardCards] = useState([])
const [homeroomAnnouncements, setHomeroomAnnouncements] = useState([])
const [announcementsLoading, setAnnouncementsLoading] = useState(true)

useEffect(() => {
setDashboardCards(
createDashboardCards(cards?.filter(c => !c.isHomeroom) || [], K5DashboardCard, {
headingLevel: 'h3',
requestTabChange
}
})
)
)
const [homeroomAnnouncements, setHomeroomAnnouncements] = useState([])
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [cards])

useEffect(() => {
fetchHomeroomAnnouncements(cards).then(setHomeroomAnnouncements)
// Cards are only ever loaded once on the page, so this only runs on mount
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
if (cards) {
setAnnouncementsLoading(true)
fetchHomeroomAnnouncements(cards)
.then(setHomeroomAnnouncements)
.catch(showFlashError(I18n.t('Failed to load announcements.')))
.finally(() => setAnnouncementsLoading(false))
}
}, [cards])

const NUM_CARD_SKELETONS = ENV?.INITIAL_NUM_K5_CARDS || 5
const skeletonCards = []
for (let i = 0; i < NUM_CARD_SKELETONS; i++) {
skeletonCards.push(
<div
className="ic-DashboardCard"
key={`card-${i}`}
style={{
height: `${CARD_SIZE_PX}px`,
minWidth: `${CARD_SIZE_PX}px`
}}
>
<LoadingSkeleton screenReaderLabel={I18n.t('Loading Card')} height="100%" width="100%" />
</div>
)
}

return (
<section
id="dashboard_page_homeroom"
style={{display: visible ? 'block' : 'none'}}
aria-hidden={!visible}
>
{homeroomAnnouncements?.length > 0 && (
<View as="section">
<HomeroomAnnouncementsLayout homeroomAnnouncements={homeroomAnnouncements} />
</View>
)}
{cards?.length > 0 && (
<View as="section">
<HomeroomAnnouncementsLayout
homeroomAnnouncements={homeroomAnnouncements}
loading={announcementsLoading}
/>
</View>
{(!cards || cards.length > 0) && (
<View as="section">
<Heading level="h2" margin="medium 0 0 0">
{I18n.t('My Subjects')}
</Heading>
{dashboardCards}
{!cards ? (
<div className="ic-DashboardCard__box">
<div className="ic-DashboardCard__box__container">{skeletonCards}</div>
</div>
) : (
dashboardCards
)}
</View>
)}
</section>
)
}

HomeroomPage.propTypes = {
cards: PropTypes.array.isRequired,
cards: PropTypes.array,
requestTabChange: PropTypes.func.isRequired,
visible: PropTypes.bool.isRequired
}
Expand Down
21 changes: 11 additions & 10 deletions ui/features/k5_dashboard/react/K5Dashboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ export const K5Dashboard = ({
assignmentsDueToday,
assignmentsMissing,
assignmentsCompletedForToday,
loadingOpportunities,
currentUser: {display_name},
loadAllOpportunities,
timeZone,
Expand All @@ -93,7 +94,7 @@ export const K5Dashboard = ({
})

useEffect(() => {
if (!cards && (currentTab === TAB_IDS.RESOURCES || currentTab === TAB_IDS.HOMEROOM)) {
if (!cards && (currentTab === TAB_IDS.HOMEROOM || currentTab === TAB_IDS.RESOURCES)) {
loadCardDashboard(setCards)
}
}, [cards, currentTab])
Expand All @@ -105,6 +106,7 @@ export const K5Dashboard = ({
assignmentsDueToday,
assignmentsMissing,
assignmentsCompletedForToday,
loadingOpportunities,
isStudent: plannerEnabled,
responsiveSize
}}
Expand All @@ -116,15 +118,13 @@ export const K5Dashboard = ({
tabs={DASHBOARD_TABS}
tabsRef={setTabsRef}
/>
{cards && (
<HomeroomPage
cards={cards}
isStudent={plannerEnabled}
requestTabChange={handleTabChange}
responsiveSize={responsiveSize}
visible={currentTab === TAB_IDS.HOMEROOM}
/>
)}
<HomeroomPage
cards={cards}
isStudent={plannerEnabled}
requestTabChange={handleTabChange}
responsiveSize={responsiveSize}
visible={currentTab === TAB_IDS.HOMEROOM}
/>
{plannerInitialized && <SchedulePage visible={currentTab === TAB_IDS.SCHEDULE} />}
{!plannerEnabled && currentTab === TAB_IDS.SCHEDULE && createTeacherPreview(timeZone)}
<GradesPage visible={currentTab === TAB_IDS.GRADES} />
Expand All @@ -139,6 +139,7 @@ K5Dashboard.propTypes = {
assignmentsDueToday: PropTypes.object.isRequired,
assignmentsMissing: PropTypes.object.isRequired,
assignmentsCompletedForToday: PropTypes.object.isRequired,
loadingOpportunities: PropTypes.bool.isRequired,
currentUser: PropTypes.shape({
display_name: PropTypes.string
}).isRequired,
Expand Down
Loading

0 comments on commit 93fb36e

Please sign in to comment.