Skip to content

Commit

Permalink
Display synced students count in auto-grading assignments (#6755)
Browse files Browse the repository at this point in the history
  • Loading branch information
acelaya authored Oct 7, 2024
1 parent 518ebaf commit 1c6e0c7
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 19 deletions.
1 change: 1 addition & 0 deletions lms/static/scripts/frontend_apps/api-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ export type StudentGradingSyncStatus = 'in_progress' | 'finished' | 'failed';
export type StudentGradingSync = {
h_userid: string;
status: StudentGradingSyncStatus;
grade: number;
};

export type GradingSyncStatus = 'scheduled' | StudentGradingSyncStatus;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
import {
Button,
LeaveIcon,
SpinnerCircleIcon,
} from '@hypothesis/frontend-shared';
import { useCallback, useMemo } from 'preact/hooks';
import { Button, LeaveIcon } from '@hypothesis/frontend-shared';
import classnames from 'classnames';
import { useCallback, useMemo, useState } from 'preact/hooks';
import { useParams } from 'wouter-preact';

import type { GradingSync, GradingSyncStatus } from '../../api-types';
Expand Down Expand Up @@ -58,6 +55,20 @@ export default function SyncGradesButton({
[lastSync],
);

const syncedStudentsCount = useMemo(
() =>
lastSync.data?.grades.filter(s => s.status !== 'in_progress').length ?? 0,
[lastSync.data?.grades],
);
const [totalStudentsToSync, setTotalStudentsToSync] = useState<number>();
const startSync = useCallback(() => {
// Right before starting, set amount of students that are going to be synced.
// This will prevent displaying a zero in the short interval between the
// list of students to sync is cleared, and the next sync check happens
setTotalStudentsToSync(studentsToSync?.length ?? 0);
updateSyncStatus('scheduled');
}, [studentsToSync?.length, updateSyncStatus]);

const buttonContent = useMemo(() => {
if (!studentsToSync || (lastSync.isLoading && !lastSync.data)) {
return 'Loading...';
Expand All @@ -67,10 +78,26 @@ export default function SyncGradesButton({
lastSync.data &&
['scheduled', 'in_progress'].includes(lastSync.data.status)
) {
// Use the amount set when a sync is started, but fall back to the amount
// of students from last sync, in case a sync was in progress when this
// was loaded
const totalStudentsBeingSynced =
totalStudentsToSync ?? lastSync.data.grades.length;

return (
<>
Syncing grades
<SpinnerCircleIcon className="ml-1.5" />
<div
className={classnames(
'border-solid border-l border-grey-5',
// Compensate the Button's padding, so that this "separator"
// covers the entire height of the Button
'-my-2 self-stretch',
)}
/>
<div className="text-grey-3 text-[0.7rem] self-center">
{syncedStudentsCount}/{totalStudentsBeingSynced}
</div>
</>
);
}
Expand Down Expand Up @@ -103,7 +130,14 @@ export default function SyncGradesButton({
}

return 'Grades synced';
}, [studentsToSync, lastSync.isLoading, lastSync.data, lastSync.error]);
}, [
studentsToSync,
lastSync.isLoading,
lastSync.data,
lastSync.error,
totalStudentsToSync,
syncedStudentsCount,
]);

const buttonDisabled =
lastSync.isLoading ||
Expand All @@ -112,10 +146,10 @@ export default function SyncGradesButton({
!studentsToSync ||
studentsToSync.length === 0;

const syncGrades = useCallback(async () => {
updateSyncStatus('scheduled');
const syncGrades = useCallback(() => {
startSync();

apiCall({
return apiCall({
authToken: api.authToken,
path: syncURL,
method: 'POST',
Expand All @@ -128,6 +162,7 @@ export default function SyncGradesButton({
}, [
api.authToken,
onSyncScheduled,
startSync,
studentsToSync,
syncURL,
updateSyncStatus,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,22 +83,39 @@ describe('SyncGradesButton', () => {
});
});

['scheduled', 'in_progress'].forEach(status => {
[
{
status: 'scheduled',
grades: [],
expectedCount: '0/0',
},
{
status: 'in_progress',
grades: [
{ status: 'in_progress' },
{ status: 'in_progress' },
{ status: 'finished' },
{ status: 'in_progress' },
{ status: 'failed' },
],
expectedCount: '2/5',
},
].forEach(({ status, grades, expectedCount }) => {
it('shows syncing text when grades are being synced', () => {
const wrapper = createComponent(studentsToSync, {
isLoading: false,
data: { status },
data: { status, grades },
});

assert.equal(buttonText(wrapper), 'Syncing grades');
assert.equal(buttonText(wrapper), `Syncing grades${expectedCount}`);
assert.isTrue(isButtonDisabled(wrapper));
});
});

it('shows syncing errors and allows to retry', () => {
const wrapper = createComponent(studentsToSync, {
isLoading: false,
data: { status: 'failed' },
data: { status: 'failed', grades: [] },
});

assert.equal(buttonText(wrapper), 'Error syncing. Click to retry');
Expand All @@ -125,7 +142,7 @@ describe('SyncGradesButton', () => {
it('shows the amount of students to be synced when current status is "finished"', () => {
const wrapper = createComponent(students, {
isLoading: false,
data: { status: 'finished' },
data: { status: 'finished', grades: [] },
});

assert.equal(buttonText(wrapper), `Sync ${expectedAmount} grades`);
Expand All @@ -146,7 +163,7 @@ describe('SyncGradesButton', () => {
it('shows grades synced when no students need to be synced', () => {
const wrapper = createComponent([], {
isLoading: false,
data: { status: 'finished' },
data: { status: 'finished', grades: [] },
});

assert.equal(buttonText(wrapper), 'Grades synced');
Expand All @@ -156,7 +173,7 @@ describe('SyncGradesButton', () => {
it('submits grades when the button is clicked, then calls onSyncScheduled', async () => {
const wrapper = createComponent(studentsToSync, {
isLoading: false,
data: { status: 'finished' },
data: { status: 'finished', grades: [] },
mutate: sinon.stub(),
});
await act(() => wrapper.find('Button').props().onClick());
Expand All @@ -180,7 +197,7 @@ describe('SyncGradesButton', () => {
const mutate = sinon.stub();
const wrapper = createComponent(studentsToSync, {
isLoading: false,
data: { status: 'finished' },
data: { status: 'finished', grades: [] },
mutate,
});
await act(() => wrapper.find('Button').props().onClick());
Expand Down

0 comments on commit 1c6e0c7

Please sign in to comment.