add checkpoint to create discussion mutation
add checkpoint to create discussion mutation. This only supports reply to entry, reply to topic, and additional replies required fields closes VICE-4104 flag=discussion_checkpoints flag=discussion_create Test Plan: - create a discussion with checkpoints - set points possible for reply to entry and reply to topic - set additional replies required - submit the discussion topic - go to edit the discussion and confirm that the discussion is checkpointed with the reply to topic, reply to entry, and additional replies required fields populated with the values set at creation - go to the rails console and confirm that the newly created discussion topic's assignment contains two sub assignments with the correct point values and tag Change-Id: I4f40c9b3abbadccab35b78661574a84a1e64ee6d Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/345228 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Product-Review: Samuel Lee <samuel.lee@instructure.com> Reviewed-by: Omar Soto-Fortuño <omar.soto@instructure.com> QA-Review: Caleb Guanzon <cguanzon@instructure.com>
This commit is contained in:
parent
fc41228955
commit
97234c71ed
|
@ -92,6 +92,7 @@ describe Mutations::CreateDiscussionTopic do
|
|||
podcastEnabled
|
||||
podcastHasStudentPosts
|
||||
isSectionSpecific
|
||||
replyToEntryRequiredCount
|
||||
groupSet {
|
||||
_id
|
||||
}
|
||||
|
@ -119,6 +120,13 @@ describe Mutations::CreateDiscussionTopic do
|
|||
title
|
||||
}
|
||||
}
|
||||
checkpoints {
|
||||
dueAt
|
||||
name
|
||||
onlyVisibleToOverrides
|
||||
pointsPossible
|
||||
tag
|
||||
}
|
||||
}
|
||||
}
|
||||
errors {
|
||||
|
@ -1096,7 +1104,18 @@ describe Mutations::CreateDiscussionTopic do
|
|||
GQL
|
||||
|
||||
result = execute_with_input_with_assignment(query)
|
||||
expect(result["errors"]).to be_nil
|
||||
discussion_topic = result.dig("data", "createDiscussionTopic", "discussionTopic")
|
||||
reply_to_topic_checkpoint = discussion_topic["assignment"]["checkpoints"].find { |checkpoint| checkpoint["tag"] == CheckpointLabels::REPLY_TO_TOPIC }
|
||||
reply_to_entry_checkpoint = discussion_topic["assignment"]["checkpoints"].find { |checkpoint| checkpoint["tag"] == CheckpointLabels::REPLY_TO_ENTRY }
|
||||
aggregate_failures do
|
||||
expect(result["errors"]).to be_nil
|
||||
expect(discussion_topic["assignment"]["checkpoints"][0]["name"]).to eq title
|
||||
expect(reply_to_topic_checkpoint).to be_truthy
|
||||
expect(reply_to_entry_checkpoint).to be_truthy
|
||||
expect(reply_to_topic_checkpoint["pointsPossible"]).to eq 10
|
||||
expect(reply_to_entry_checkpoint["pointsPossible"]).to eq 15
|
||||
expect(discussion_topic["replyToEntryRequiredCount"]).to eq 3
|
||||
end
|
||||
end
|
||||
|
||||
it "successfully creates a discussion topic with checkpoints using dueAt, lockAt, unlockAt" do
|
||||
|
|
|
@ -1717,6 +1717,48 @@ describe "discussions" do
|
|||
expect(assignment.only_visible_to_overrides).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context "checkpoints" do
|
||||
before do
|
||||
course.root_account.enable_feature!(:discussion_checkpoints)
|
||||
end
|
||||
|
||||
it "successfully creates a discussion topic with checkpoints" do
|
||||
get "/courses/#{course.id}/discussion_topics/new"
|
||||
|
||||
title = "Graded Discussion Topic with checkpoints"
|
||||
|
||||
f("input[placeholder='Topic Title']").send_keys title
|
||||
|
||||
force_click_native('input[type=checkbox][value="graded"]')
|
||||
wait_for_ajaximations
|
||||
|
||||
force_click_native('input[type=checkbox][value="checkpoints"]')
|
||||
|
||||
f("input[data-testid='points-possible-input-reply-to-topic']").send_keys "5"
|
||||
f("input[data-testid='reply-to-entry-required-count']").send_keys :backspace
|
||||
f("input[data-testid='reply-to-entry-required-count']").send_keys 3
|
||||
f("input[data-testid='points-possible-input-reply-to-entry']").send_keys "7"
|
||||
|
||||
f("button[data-testid='save-and-publish-button']").click
|
||||
wait_for_ajaximations
|
||||
|
||||
dt = DiscussionTopic.last
|
||||
expect(dt.reply_to_entry_required_count).to eq 3
|
||||
|
||||
assignment = Assignment.last
|
||||
expect(assignment.has_sub_assignments?).to be true
|
||||
|
||||
sub_assignments = SubAssignment.where(parent_assignment_id: assignment.id)
|
||||
sub_assignment1 = sub_assignments.find_by(sub_assignment_tag: CheckpointLabels::REPLY_TO_TOPIC)
|
||||
sub_assignment2 = sub_assignments.find_by(sub_assignment_tag: CheckpointLabels::REPLY_TO_ENTRY)
|
||||
|
||||
expect(sub_assignment1.sub_assignment_tag).to eq "reply_to_topic"
|
||||
expect(sub_assignment1.points_possible).to eq 5
|
||||
expect(sub_assignment2.sub_assignment_tag).to eq "reply_to_entry"
|
||||
expect(sub_assignment2.points_possible).to eq 7
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -42,6 +42,7 @@ export const CREATE_DISCUSSION_TOPIC = gql`
|
|||
$specificSections: String
|
||||
$groupCategoryId: ID
|
||||
$assignment: AssignmentCreate
|
||||
$checkpoints: [DiscussionCheckpoints!]
|
||||
$fileId: ID
|
||||
) {
|
||||
createDiscussionTopic(
|
||||
|
@ -66,6 +67,7 @@ export const CREATE_DISCUSSION_TOPIC = gql`
|
|||
specificSections: $specificSections
|
||||
groupCategoryId: $groupCategoryId
|
||||
assignment: $assignment
|
||||
checkpoints: $checkpoints
|
||||
fileId: $fileId
|
||||
}
|
||||
) {
|
||||
|
@ -109,6 +111,13 @@ export const CREATE_DISCUSSION_TOPIC = gql`
|
|||
dueAt
|
||||
enabled
|
||||
}
|
||||
checkpoints {
|
||||
dueAt
|
||||
name
|
||||
onlyVisibleToOverrides
|
||||
pointsPossible
|
||||
tag
|
||||
}
|
||||
}
|
||||
attachment {
|
||||
...Attachment
|
||||
|
|
|
@ -51,6 +51,8 @@ import {
|
|||
defaultEveryoneElseOption,
|
||||
masteryPathsOption,
|
||||
useShouldShowContent,
|
||||
REPLY_TO_TOPIC,
|
||||
REPLY_TO_ENTRY,
|
||||
} from '../../util/constants'
|
||||
|
||||
import {AttachmentDisplay} from '@canvas/discussions/react/components/AttachmentDisplay/AttachmentDisplay'
|
||||
|
@ -59,7 +61,7 @@ import {UsageRightsContainer} from '../../containers/usageRights/UsageRightsCont
|
|||
import {AlertManagerContext} from '@canvas/alerts/react/AlertManager'
|
||||
import {ScreenReaderContent} from '@instructure/ui-a11y-content'
|
||||
|
||||
import {prepareAssignmentPayload} from '../../util/payloadPreparations'
|
||||
import {prepareAssignmentPayload, prepareCheckpointsPayload} from '../../util/payloadPreparations'
|
||||
import {validateTitle, validateFormFields} from '../../util/formValidation'
|
||||
|
||||
import {
|
||||
|
@ -227,13 +229,21 @@ export default function DiscussionTopicForm({
|
|||
const [isCheckpoints, setIsCheckpoints] = useState(
|
||||
currentDiscussionTopic?.assignment?.hasSubAssignments || false
|
||||
)
|
||||
const getCheckpointsPointsPossible = (checkpointLabel) => {
|
||||
const checkpoint = currentDiscussionTopic?.assignment?.checkpoints?.find(c => c.tag === checkpointLabel)
|
||||
const getCheckpointsPointsPossible = checkpointLabel => {
|
||||
const checkpoint = currentDiscussionTopic?.assignment?.checkpoints?.find(
|
||||
c => c.tag === checkpointLabel
|
||||
)
|
||||
return checkpoint ? checkpoint.pointsPossible : 0
|
||||
}
|
||||
const [pointsPossibleReplyToTopic, setPointsPossibleReplyToTopic] = useState(getCheckpointsPointsPossible('reply_to_topic'))
|
||||
const [pointsPossibleReplyToEntry, setPointsPossibleReplyToEntry] = useState(getCheckpointsPointsPossible('reply_to_entry'))
|
||||
const [replyToEntryRequiredCount, setReplyToEntryRequiredCount] = useState(currentDiscussionTopic?.replyToEntryRequiredCount || 1)
|
||||
const [pointsPossibleReplyToTopic, setPointsPossibleReplyToTopic] = useState(
|
||||
getCheckpointsPointsPossible(REPLY_TO_TOPIC)
|
||||
)
|
||||
const [pointsPossibleReplyToEntry, setPointsPossibleReplyToEntry] = useState(
|
||||
getCheckpointsPointsPossible(REPLY_TO_ENTRY)
|
||||
)
|
||||
const [replyToEntryRequiredCount, setReplyToEntryRequiredCount] = useState(
|
||||
currentDiscussionTopic?.replyToEntryRequiredCount || 1
|
||||
)
|
||||
|
||||
const assignmentDueDateContext = {
|
||||
assignedInfoList,
|
||||
|
@ -360,7 +370,14 @@ export default function DiscussionTopicForm({
|
|||
peerReviewsPerStudent,
|
||||
peerReviewDueDate,
|
||||
intraGroupPeerReviews,
|
||||
masteryPathsOption
|
||||
masteryPathsOption,
|
||||
isCheckpoints
|
||||
),
|
||||
checkpoints: prepareCheckpointsPayload(
|
||||
pointsPossibleReplyToTopic,
|
||||
pointsPossibleReplyToEntry,
|
||||
replyToEntryRequiredCount,
|
||||
isCheckpoints
|
||||
),
|
||||
groupCategoryId: isGroupDiscussion ? groupCategoryId : null,
|
||||
specificSections: shouldShowPostToSectionOption ? sectionIdsToPostTo.join() : 'all',
|
||||
|
|
|
@ -22,8 +22,9 @@ import userEvent from '@testing-library/user-event'
|
|||
import React from 'react'
|
||||
import DiscussionTopicForm from '../DiscussionTopicForm'
|
||||
import {DiscussionTopic} from '../../../../graphql/DiscussionTopic'
|
||||
import { Assignment } from '../../../../graphql/Assignment'
|
||||
import {Assignment} from '../../../../graphql/Assignment'
|
||||
import {GroupSet} from '../../../../graphql/GroupSet'
|
||||
import {REPLY_TO_TOPIC, REPLY_TO_ENTRY} from '../../../util/constants'
|
||||
|
||||
jest.mock('@canvas/rce/react/CanvasRce')
|
||||
|
||||
|
@ -455,19 +456,21 @@ describe('DiscussionTopicForm', () => {
|
|||
})
|
||||
it('renders the checkpoints checkbox as selected when there are existing checkpoints', () => {
|
||||
const {getByTestId} = setup({
|
||||
currentDiscussionTopic: DiscussionTopic.mock({assignment: Assignment.mock({hasSubAssignments: true})}),
|
||||
currentDiscussionTopic: DiscussionTopic.mock({
|
||||
assignment: Assignment.mock({hasSubAssignments: true}),
|
||||
}),
|
||||
})
|
||||
const checkbox = getByTestId('checkpoints-checkbox')
|
||||
expect(checkbox.checked).toBe(true)
|
||||
})
|
||||
describe('Checkpoints Settings', () => {
|
||||
let getByTestId, getByLabelText;
|
||||
let getByTestId, getByLabelText
|
||||
|
||||
const setupCheckpoints = (setupFunction) => {
|
||||
const setupCheckpoints = setupFunction => {
|
||||
const discussionTopicSetup = setupFunction
|
||||
|
||||
getByTestId = discussionTopicSetup.getByTestId;
|
||||
getByLabelText = discussionTopicSetup.getByLabelText;
|
||||
getByTestId = discussionTopicSetup.getByTestId
|
||||
getByLabelText = discussionTopicSetup.getByLabelText
|
||||
|
||||
getByLabelText('Graded').click()
|
||||
|
||||
|
@ -570,24 +573,28 @@ describe('DiscussionTopicForm', () => {
|
|||
})
|
||||
it('sets the correct checkpoint settings values when there are existing checkpoints', () => {
|
||||
const {getByTestId} = setup({
|
||||
currentDiscussionTopic: DiscussionTopic.mock({replyToEntryRequiredCount: 5, assignment: Assignment.mock({
|
||||
hasSubAssignments: true,
|
||||
checkpoints: [{
|
||||
"dueAt": null,
|
||||
"name": "checkpoint discussion",
|
||||
"onlyVisibleToOverrides": false,
|
||||
"pointsPossible": 6,
|
||||
"tag": "reply_to_topic"
|
||||
},
|
||||
{
|
||||
"dueAt": null,
|
||||
"name": "checkpoint discussion",
|
||||
"onlyVisibleToOverrides": false,
|
||||
"pointsPossible": 7,
|
||||
"tag": "reply_to_entry"
|
||||
}
|
||||
]}
|
||||
)}),
|
||||
currentDiscussionTopic: DiscussionTopic.mock({
|
||||
replyToEntryRequiredCount: 5,
|
||||
assignment: Assignment.mock({
|
||||
hasSubAssignments: true,
|
||||
checkpoints: [
|
||||
{
|
||||
dueAt: null,
|
||||
name: 'checkpoint discussion',
|
||||
onlyVisibleToOverrides: false,
|
||||
pointsPossible: 6,
|
||||
tag: REPLY_TO_TOPIC,
|
||||
},
|
||||
{
|
||||
dueAt: null,
|
||||
name: 'checkpoint discussion',
|
||||
onlyVisibleToOverrides: false,
|
||||
pointsPossible: 7,
|
||||
tag: REPLY_TO_ENTRY,
|
||||
},
|
||||
],
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
const numberInputReplyToTopic = getByTestId('points-possible-input-reply-to-topic')
|
||||
|
@ -599,4 +606,4 @@ describe('DiscussionTopicForm', () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -63,6 +63,9 @@ export const ASSIGNMENT_OVERRIDE_GRAPHQL_TYPENAMES = {
|
|||
export const minimumReplyToEntryRequiredCount = 1
|
||||
export const maximumReplyToEntryRequiredCount = 10
|
||||
|
||||
export const REPLY_TO_TOPIC = 'reply_to_topic'
|
||||
export const REPLY_TO_ENTRY = 'reply_to_entry'
|
||||
|
||||
export const useShouldShowContent = (
|
||||
isGraded,
|
||||
isAnnouncement,
|
||||
|
|
|
@ -16,6 +16,8 @@
|
|||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
import {REPLY_TO_TOPIC, REPLY_TO_ENTRY} from './constants'
|
||||
|
||||
const prepareOverride = (
|
||||
overrideDueDate,
|
||||
overrideAvailableUntil,
|
||||
|
@ -149,6 +151,30 @@ const preparePeerReviewPayload = (
|
|||
}
|
||||
}
|
||||
|
||||
export const prepareCheckpointsPayload = (
|
||||
pointsPossibleReplyToTopic,
|
||||
pointsPossibleReplyToEntry,
|
||||
replyToEntryRequiredCount,
|
||||
isCheckpoints
|
||||
) => {
|
||||
return isCheckpoints
|
||||
? [
|
||||
{
|
||||
checkpointLabel: REPLY_TO_TOPIC,
|
||||
pointsPossible: pointsPossibleReplyToTopic,
|
||||
dates: [],
|
||||
repliesRequired: replyToEntryRequiredCount,
|
||||
},
|
||||
{
|
||||
checkpointLabel: REPLY_TO_ENTRY,
|
||||
pointsPossible: pointsPossibleReplyToEntry,
|
||||
dates: [],
|
||||
repliesRequired: replyToEntryRequiredCount,
|
||||
},
|
||||
]
|
||||
: []
|
||||
}
|
||||
|
||||
export const prepareAssignmentPayload = (
|
||||
isEditing,
|
||||
title,
|
||||
|
@ -165,7 +191,8 @@ export const prepareAssignmentPayload = (
|
|||
peerReviewsPerStudent,
|
||||
peerReviewDueDate,
|
||||
intraGroupPeerReviews,
|
||||
masteryPathsOption
|
||||
masteryPathsOption,
|
||||
isCheckpoints
|
||||
) => {
|
||||
// Return null immediately if the assignment is not graded
|
||||
if (!isGraded) return null
|
||||
|
@ -176,10 +203,8 @@ export const prepareAssignmentPayload = (
|
|||
info.assignedList.includes(defaultEveryoneOption.assetCode) ||
|
||||
info.assignedList.includes(defaultEveryoneElseOption.assetCode)
|
||||
) || {}
|
||||
|
||||
// Common payload properties for graded assignments
|
||||
let payload = {
|
||||
pointsPossible,
|
||||
postToSis,
|
||||
gradingType: displayGradeAs,
|
||||
assignmentGroupId: assignmentGroup || null,
|
||||
|
@ -195,11 +220,19 @@ export const prepareAssignmentPayload = (
|
|||
defaultEveryoneOption,
|
||||
masteryPathsOption
|
||||
),
|
||||
dueAt: everyoneOverride.dueDate || null,
|
||||
lockAt: everyoneOverride.availableUntil || null,
|
||||
unlockAt: everyoneOverride.availableFrom || null,
|
||||
onlyVisibleToOverrides: !Object.keys(everyoneOverride).length,
|
||||
gradingStandardId: gradingSchemeId || null,
|
||||
forCheckpoints: isCheckpoints,
|
||||
}
|
||||
// Additional properties if graded assignment is not checkpointed
|
||||
if (!isCheckpoints) {
|
||||
payload = {
|
||||
...payload,
|
||||
pointsPossible,
|
||||
dueAt: everyoneOverride.dueDate || null,
|
||||
lockAt: everyoneOverride.availableUntil || null,
|
||||
unlockAt: everyoneOverride.availableFrom || null,
|
||||
}
|
||||
}
|
||||
// Additional properties for creation of a graded assignment
|
||||
if (!isEditing) {
|
||||
|
|
Loading…
Reference in New Issue