add checkpoints to assignments index student
flag=react_discussions_post flag=discussion_checkpoints flag=assignments_2_student fixes VICE-4289 test plan: - turn on all feature flags listed above - create a checkpointed discussion with due dates , points possible, and replies required for checkpoints - as a student, visit assignments index - verify checkpointed discussions match designs Change-Id: I90decc73e193c37926f5065137114a23b4809fdb Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/350105 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> QA-Review: Chawn Neal <chawn.neal@instructure.com> Reviewed-by: Jason Gillett <jason.gillett@instructure.com> Product-Review: Sam Garza <sam.garza@instructure.com>
This commit is contained in:
parent
02debf1861
commit
6612087944
|
@ -180,6 +180,8 @@ export type Assignment = Readonly<{
|
|||
automatic_peer_reviews: boolean
|
||||
can_duplicate: boolean
|
||||
course_id: string
|
||||
checkpoints: Checkpoint[]
|
||||
discussion_topic: DiscussionTopic
|
||||
due_date_required: boolean
|
||||
final_grader_id: null | string
|
||||
grade_group_students_individually: boolean
|
||||
|
@ -646,3 +648,27 @@ export type ReleaseNote = {
|
|||
date: string
|
||||
new: boolean
|
||||
}
|
||||
|
||||
export type DiscussionTopic = {
|
||||
reply_to_entry_required_count: number
|
||||
}
|
||||
|
||||
export type Checkpoint = {
|
||||
due_at: string | null
|
||||
name: string
|
||||
only_visible_to_overrides: boolean
|
||||
overrides: CheckpointOverride[]
|
||||
points_possible: number
|
||||
tag: string
|
||||
}
|
||||
|
||||
export type CheckpointOverride = {
|
||||
all_day: boolean
|
||||
all_day_date: string
|
||||
assignment_id: string
|
||||
due_at: string
|
||||
id: string
|
||||
student_ids: string[]
|
||||
title: string
|
||||
unassign_item: boolean
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import DirectShareCourseTray from '@canvas/direct-sharing/react/components/Direc
|
|||
import DirectShareUserModal from '@canvas/direct-sharing/react/components/DirectShareUserModal'
|
||||
import {scoreToPercentage} from '@canvas/grading/GradeCalculationHelper'
|
||||
import {useScope as useI18nScope} from '@canvas/i18n'
|
||||
import {ListViewCheckpoints} from '@canvas/list-view-checkpoints/react/ListViewCheckpoints'
|
||||
import LockIconView from '@canvas/lock-icon'
|
||||
import * as MoveItem from '@canvas/move-item-tray'
|
||||
import PublishIconView from '@canvas/publish-icon-view'
|
||||
|
@ -43,6 +44,7 @@ import scoreTemplate from '../../jst/_assignmentListItemScore.handlebars'
|
|||
import AssignmentKeyBindingsMixin from '../mixins/AssignmentKeyBindingsMixin'
|
||||
import CreateAssignmentView from './CreateAssignmentView'
|
||||
import ItemAssignToTray from '@canvas/context-modules/differentiated-modules/react/Item/ItemAssignToTray'
|
||||
import {captureException} from '@sentry/browser'
|
||||
|
||||
const I18n = useI18nScope('AssignmentListItemView')
|
||||
|
||||
|
@ -323,7 +325,28 @@ export default AssignmentListItemView = (function () {
|
|||
}
|
||||
|
||||
const {attributes = {}} = this.model
|
||||
const {assessment_requests: assessmentRequests} = attributes
|
||||
const {assessment_requests: assessmentRequests, checkpoints} = attributes
|
||||
|
||||
if (checkpoints && checkpoints.length && !this.canManage()) {
|
||||
const checkpointsElem =
|
||||
this.$el.find(`#assignment_student_checkpoints_${this.model.id}`) ?? []
|
||||
const mountPoint = checkpointsElem[0]
|
||||
|
||||
try {
|
||||
ReactDOM.render(
|
||||
React.createElement(ListViewCheckpoints, {
|
||||
assignment: attributes,
|
||||
}),
|
||||
mountPoint
|
||||
)
|
||||
} catch (error) {
|
||||
const errorMessage = I18n.t('Checkpoints mount point element not found')
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(errorMessage, error)
|
||||
captureException(new Error(errorMessage), error)
|
||||
}
|
||||
}
|
||||
|
||||
if (assessmentRequests && assessmentRequests.length) {
|
||||
const peerReviewElem =
|
||||
this.$el.find(`#assignment_student_peer_review_${this.model.id}`) ?? []
|
||||
|
|
|
@ -508,6 +508,7 @@
|
|||
{{#if canManage}}<form data-view="edit-assignment" class="form-dialog"></form>{{/if}}
|
||||
{{/if}}{{/if}}{{/if}}{{/if}}{{/if}}{{/if}}{{/if}}{{/if}}
|
||||
</div>
|
||||
<div id="assignment_student_checkpoints_{{id}}"></div>
|
||||
<div id="assignment_student_peer_review_{{id}}">
|
||||
<div id="assign-to-mount-point"></div>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"name": "@canvas/list-view-checkpoints",
|
||||
"private": true,
|
||||
"version": "1.0.0"
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright (C) 2024 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {IconArrowNestLine} from '@instructure/ui-icons'
|
||||
import {View} from '@instructure/ui-view'
|
||||
import {useScope as useI18nScope} from '@canvas/i18n'
|
||||
import type {Assignment, Checkpoint} from '../../../api.d'
|
||||
|
||||
const I18n = useI18nScope('assignment')
|
||||
|
||||
const REPLY_TO_TOPIC: string = 'reply_to_topic'
|
||||
|
||||
export type AssignmentCheckpoints = Pick<Assignment, 'id' | 'checkpoints' | 'discussion_topic'>
|
||||
|
||||
export type StudentViewCheckpointProps = {
|
||||
assignment: AssignmentCheckpoints
|
||||
}
|
||||
|
||||
export type CheckpointProps = {
|
||||
assignment: AssignmentCheckpoints
|
||||
checkpoint: Checkpoint
|
||||
}
|
||||
|
||||
const createDateTimeFormatter = () => {
|
||||
return Intl.DateTimeFormat(ENV.LOCALE, {
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
timeZone: ENV.TIMEZONE,
|
||||
})
|
||||
}
|
||||
|
||||
const dateFormatter = createDateTimeFormatter()
|
||||
|
||||
export const ListViewCheckpoints = ({assignment}: StudentViewCheckpointProps) => {
|
||||
return (
|
||||
<>
|
||||
{assignment.checkpoints.map(checkpoint => (
|
||||
<CheckpointItem
|
||||
checkpoint={checkpoint}
|
||||
assignment={assignment}
|
||||
key={`${assignment.id}_${checkpoint.tag}`}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const CheckpointItem = React.memo(({checkpoint, assignment}: CheckpointProps) => {
|
||||
const getCheckpointDueDate = () => {
|
||||
if (checkpoint.due_at) {
|
||||
return dateFormatter.format(new Date(checkpoint.due_at))
|
||||
}
|
||||
|
||||
// Once VICE-4350 is completed, modify this to find the due date from the checkpoint overrides
|
||||
for (const override of checkpoint.overrides) {
|
||||
if (
|
||||
override.student_ids &&
|
||||
ENV.current_user_id &&
|
||||
override.student_ids.includes(ENV.current_user_id)
|
||||
) {
|
||||
return dateFormatter.format(new Date(override.due_at))
|
||||
}
|
||||
}
|
||||
|
||||
return I18n.t('No Due Date')
|
||||
}
|
||||
|
||||
const renderCheckpointTitle = () => {
|
||||
if (checkpoint.tag === REPLY_TO_TOPIC) {
|
||||
return I18n.t('Reply To Topic')
|
||||
} else {
|
||||
// if it's not reply to topic, it must be reply to entry
|
||||
const translatedReplyToEntryRequiredCount = I18n.n(
|
||||
assignment.discussion_topic.reply_to_entry_required_count
|
||||
)
|
||||
return I18n.t('Required Replies (%{requiredReplies})', {
|
||||
requiredReplies: translatedReplyToEntryRequiredCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<li className="context_module_item student-view cannot-duplicate indent_1">
|
||||
<div className="ig-row">
|
||||
<div className="ig-row__layout">
|
||||
<span className="type_icon display_icons" style={{fontSize: '1.125rem'}}>
|
||||
<View as="span" margin="0 0 0 medium">
|
||||
<IconArrowNestLine />
|
||||
</View>
|
||||
</span>
|
||||
<div className="ig-info">
|
||||
<span
|
||||
style={{color: 'var(--ic-brand-font-color-dark)'}}
|
||||
className="item_name ig-title title"
|
||||
data-testid={`${assignment.id}_${checkpoint.tag}_title`}
|
||||
>
|
||||
{renderCheckpointTitle()}
|
||||
</span>
|
||||
|
||||
<div className="ig-details">
|
||||
<div
|
||||
className="ig-details__item"
|
||||
data-testid={`${assignment.id}_${checkpoint.tag}_due_date`}
|
||||
>
|
||||
{getCheckpointDueDate()}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* Copyright (C) 2024 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
import {render} from '@testing-library/react'
|
||||
import {ListViewCheckpoints} from '../ListViewCheckpoints'
|
||||
import {
|
||||
checkpointedAssignmentNoDueDates,
|
||||
checkpointedAssignmentWithDueDates,
|
||||
checkpointedAssignmentWithOverrides,
|
||||
} from './mocks'
|
||||
|
||||
describe('ListViewCheckpoints', () => {
|
||||
it('renders the ListViewCheckpoints component with the correct checkpoint titles', async () => {
|
||||
const {container, getByTestId} = render(
|
||||
<ListViewCheckpoints {...checkpointedAssignmentNoDueDates} />
|
||||
)
|
||||
expect(container.querySelectorAll('li')).toHaveLength(2)
|
||||
|
||||
expect(getByTestId('1_reply_to_topic_title').textContent).toEqual('Reply To Topic')
|
||||
expect(getByTestId('1_reply_to_entry_title').textContent).toEqual('Required Replies (4)')
|
||||
})
|
||||
|
||||
describe('due date', () => {
|
||||
it('renders the ListViewCheckpoints components with No Due Date if the checkpoint as no due date', () => {
|
||||
const {getByTestId} = render(<ListViewCheckpoints {...checkpointedAssignmentNoDueDates} />)
|
||||
|
||||
expect(getByTestId('1_reply_to_topic_due_date').textContent).toEqual('No Due Date')
|
||||
expect(getByTestId('1_reply_to_entry_due_date').textContent).toEqual('No Due Date')
|
||||
})
|
||||
|
||||
it('renders the ListViewCheckpoints components with the formatted due dates if the checkpoint due_at field is populated', () => {
|
||||
const {getByTestId} = render(<ListViewCheckpoints {...checkpointedAssignmentWithDueDates} />)
|
||||
|
||||
expect(getByTestId('1_reply_to_topic_due_date').textContent).toEqual('Jun 2')
|
||||
expect(getByTestId('1_reply_to_entry_due_date').textContent).toEqual('Jun 4')
|
||||
})
|
||||
|
||||
// Once VICE-4350 is completed, modify this test to reflect the new changes for finding the due date from the checkpoint overrides
|
||||
it('renders the ListViewCheckpoints components with the formatted due dates if the checkpoint has overrides', () => {
|
||||
ENV.current_user_id = '1'
|
||||
|
||||
const {getByTestId} = render(<ListViewCheckpoints {...checkpointedAssignmentWithOverrides} />)
|
||||
|
||||
expect(getByTestId('1_reply_to_topic_due_date').textContent).toEqual('Jun 2')
|
||||
expect(getByTestId('1_reply_to_entry_due_date').textContent).toEqual('Jun 4')
|
||||
})
|
||||
})
|
||||
})
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (C) 2024 - present Instructure, Inc.
|
||||
*
|
||||
* This file is part of Canvas.
|
||||
*
|
||||
* Canvas is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3 of the License.
|
||||
*
|
||||
* Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||
* A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License along
|
||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
export const checkpointedAssignmentNoDueDates = {
|
||||
assignment: {
|
||||
id: '1',
|
||||
course_id: '1',
|
||||
name: 'Checkpoint Assignment',
|
||||
checkpoints: [
|
||||
{
|
||||
due_at: null,
|
||||
name: 'Checkpoint Assignment',
|
||||
only_visible_to_overrides: false,
|
||||
overrides: [],
|
||||
points_possible: 1,
|
||||
tag: 'reply_to_topic',
|
||||
},
|
||||
{
|
||||
due_at: null,
|
||||
name: 'Checkpoint Assignment',
|
||||
only_visible_to_overrides: false,
|
||||
overrides: [],
|
||||
points_possible: 1,
|
||||
tag: 'reply_to_entry',
|
||||
},
|
||||
],
|
||||
discussion_topic: {
|
||||
reply_to_entry_required_count: 4,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const checkpointedAssignmentWithDueDates = {
|
||||
assignment: {
|
||||
id: '1',
|
||||
course_id: '1',
|
||||
name: 'Checkpoint Assignment',
|
||||
checkpoints: [
|
||||
{
|
||||
due_at: '2024-06-02T17:43:13Z',
|
||||
name: 'Checkpoint Assignment',
|
||||
only_visible_to_overrides: false,
|
||||
overrides: [],
|
||||
points_possible: 1,
|
||||
tag: 'reply_to_topic',
|
||||
},
|
||||
{
|
||||
due_at: '2024-06-04T17:43:13Z',
|
||||
name: 'Checkpoint Assignment',
|
||||
only_visible_to_overrides: false,
|
||||
overrides: [],
|
||||
points_possible: 1,
|
||||
tag: 'reply_to_entry',
|
||||
},
|
||||
],
|
||||
discussion_topic: {
|
||||
reply_to_entry_required_count: 4,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
export const checkpointedAssignmentWithOverrides = {
|
||||
assignment: {
|
||||
id: '1',
|
||||
course_id: '1',
|
||||
name: 'Checkpoint Assignment',
|
||||
checkpoints: [
|
||||
{
|
||||
due_at: null,
|
||||
name: 'Checkpoint Assignment',
|
||||
only_visible_to_overrides: false,
|
||||
overrides: [
|
||||
{
|
||||
due_at: '2024-06-02T17:43:13Z',
|
||||
student_ids: ['1'],
|
||||
all_day: false,
|
||||
all_day_date: '',
|
||||
assignment_id: '1',
|
||||
id: '1',
|
||||
title: '',
|
||||
unassign_item: false,
|
||||
},
|
||||
],
|
||||
points_possible: 1,
|
||||
tag: 'reply_to_topic',
|
||||
},
|
||||
{
|
||||
due_at: null,
|
||||
name: 'Checkpoint Assignment',
|
||||
only_visible_to_overrides: false,
|
||||
overrides: [
|
||||
{
|
||||
due_at: '2024-06-04T17:43:13Z',
|
||||
student_ids: ['1'],
|
||||
all_day: false,
|
||||
all_day_date: '',
|
||||
assignment_id: '1',
|
||||
id: '1',
|
||||
title: '',
|
||||
unassign_item: false,
|
||||
},
|
||||
],
|
||||
points_possible: 1,
|
||||
tag: 'reply_to_entry',
|
||||
},
|
||||
],
|
||||
discussion_topic: {
|
||||
reply_to_entry_required_count: 4,
|
||||
},
|
||||
},
|
||||
}
|
Loading…
Reference in New Issue