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
|
podcastEnabled
|
||||||
podcastHasStudentPosts
|
podcastHasStudentPosts
|
||||||
isSectionSpecific
|
isSectionSpecific
|
||||||
|
replyToEntryRequiredCount
|
||||||
groupSet {
|
groupSet {
|
||||||
_id
|
_id
|
||||||
}
|
}
|
||||||
|
@ -119,6 +120,13 @@ describe Mutations::CreateDiscussionTopic do
|
||||||
title
|
title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
checkpoints {
|
||||||
|
dueAt
|
||||||
|
name
|
||||||
|
onlyVisibleToOverrides
|
||||||
|
pointsPossible
|
||||||
|
tag
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
errors {
|
errors {
|
||||||
|
@ -1096,7 +1104,18 @@ describe Mutations::CreateDiscussionTopic do
|
||||||
GQL
|
GQL
|
||||||
|
|
||||||
result = execute_with_input_with_assignment(query)
|
result = execute_with_input_with_assignment(query)
|
||||||
|
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(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
|
end
|
||||||
|
|
||||||
it "successfully creates a discussion topic with checkpoints using dueAt, lockAt, unlockAt" do
|
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
|
expect(assignment.only_visible_to_overrides).to be true
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -42,6 +42,7 @@ export const CREATE_DISCUSSION_TOPIC = gql`
|
||||||
$specificSections: String
|
$specificSections: String
|
||||||
$groupCategoryId: ID
|
$groupCategoryId: ID
|
||||||
$assignment: AssignmentCreate
|
$assignment: AssignmentCreate
|
||||||
|
$checkpoints: [DiscussionCheckpoints!]
|
||||||
$fileId: ID
|
$fileId: ID
|
||||||
) {
|
) {
|
||||||
createDiscussionTopic(
|
createDiscussionTopic(
|
||||||
|
@ -66,6 +67,7 @@ export const CREATE_DISCUSSION_TOPIC = gql`
|
||||||
specificSections: $specificSections
|
specificSections: $specificSections
|
||||||
groupCategoryId: $groupCategoryId
|
groupCategoryId: $groupCategoryId
|
||||||
assignment: $assignment
|
assignment: $assignment
|
||||||
|
checkpoints: $checkpoints
|
||||||
fileId: $fileId
|
fileId: $fileId
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
@ -109,6 +111,13 @@ export const CREATE_DISCUSSION_TOPIC = gql`
|
||||||
dueAt
|
dueAt
|
||||||
enabled
|
enabled
|
||||||
}
|
}
|
||||||
|
checkpoints {
|
||||||
|
dueAt
|
||||||
|
name
|
||||||
|
onlyVisibleToOverrides
|
||||||
|
pointsPossible
|
||||||
|
tag
|
||||||
|
}
|
||||||
}
|
}
|
||||||
attachment {
|
attachment {
|
||||||
...Attachment
|
...Attachment
|
||||||
|
|
|
@ -51,6 +51,8 @@ import {
|
||||||
defaultEveryoneElseOption,
|
defaultEveryoneElseOption,
|
||||||
masteryPathsOption,
|
masteryPathsOption,
|
||||||
useShouldShowContent,
|
useShouldShowContent,
|
||||||
|
REPLY_TO_TOPIC,
|
||||||
|
REPLY_TO_ENTRY,
|
||||||
} from '../../util/constants'
|
} from '../../util/constants'
|
||||||
|
|
||||||
import {AttachmentDisplay} from '@canvas/discussions/react/components/AttachmentDisplay/AttachmentDisplay'
|
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 {AlertManagerContext} from '@canvas/alerts/react/AlertManager'
|
||||||
import {ScreenReaderContent} from '@instructure/ui-a11y-content'
|
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 {validateTitle, validateFormFields} from '../../util/formValidation'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -227,13 +229,21 @@ export default function DiscussionTopicForm({
|
||||||
const [isCheckpoints, setIsCheckpoints] = useState(
|
const [isCheckpoints, setIsCheckpoints] = useState(
|
||||||
currentDiscussionTopic?.assignment?.hasSubAssignments || false
|
currentDiscussionTopic?.assignment?.hasSubAssignments || false
|
||||||
)
|
)
|
||||||
const getCheckpointsPointsPossible = (checkpointLabel) => {
|
const getCheckpointsPointsPossible = checkpointLabel => {
|
||||||
const checkpoint = currentDiscussionTopic?.assignment?.checkpoints?.find(c => c.tag === checkpointLabel)
|
const checkpoint = currentDiscussionTopic?.assignment?.checkpoints?.find(
|
||||||
|
c => c.tag === checkpointLabel
|
||||||
|
)
|
||||||
return checkpoint ? checkpoint.pointsPossible : 0
|
return checkpoint ? checkpoint.pointsPossible : 0
|
||||||
}
|
}
|
||||||
const [pointsPossibleReplyToTopic, setPointsPossibleReplyToTopic] = useState(getCheckpointsPointsPossible('reply_to_topic'))
|
const [pointsPossibleReplyToTopic, setPointsPossibleReplyToTopic] = useState(
|
||||||
const [pointsPossibleReplyToEntry, setPointsPossibleReplyToEntry] = useState(getCheckpointsPointsPossible('reply_to_entry'))
|
getCheckpointsPointsPossible(REPLY_TO_TOPIC)
|
||||||
const [replyToEntryRequiredCount, setReplyToEntryRequiredCount] = useState(currentDiscussionTopic?.replyToEntryRequiredCount || 1)
|
)
|
||||||
|
const [pointsPossibleReplyToEntry, setPointsPossibleReplyToEntry] = useState(
|
||||||
|
getCheckpointsPointsPossible(REPLY_TO_ENTRY)
|
||||||
|
)
|
||||||
|
const [replyToEntryRequiredCount, setReplyToEntryRequiredCount] = useState(
|
||||||
|
currentDiscussionTopic?.replyToEntryRequiredCount || 1
|
||||||
|
)
|
||||||
|
|
||||||
const assignmentDueDateContext = {
|
const assignmentDueDateContext = {
|
||||||
assignedInfoList,
|
assignedInfoList,
|
||||||
|
@ -360,7 +370,14 @@ export default function DiscussionTopicForm({
|
||||||
peerReviewsPerStudent,
|
peerReviewsPerStudent,
|
||||||
peerReviewDueDate,
|
peerReviewDueDate,
|
||||||
intraGroupPeerReviews,
|
intraGroupPeerReviews,
|
||||||
masteryPathsOption
|
masteryPathsOption,
|
||||||
|
isCheckpoints
|
||||||
|
),
|
||||||
|
checkpoints: prepareCheckpointsPayload(
|
||||||
|
pointsPossibleReplyToTopic,
|
||||||
|
pointsPossibleReplyToEntry,
|
||||||
|
replyToEntryRequiredCount,
|
||||||
|
isCheckpoints
|
||||||
),
|
),
|
||||||
groupCategoryId: isGroupDiscussion ? groupCategoryId : null,
|
groupCategoryId: isGroupDiscussion ? groupCategoryId : null,
|
||||||
specificSections: shouldShowPostToSectionOption ? sectionIdsToPostTo.join() : 'all',
|
specificSections: shouldShowPostToSectionOption ? sectionIdsToPostTo.join() : 'all',
|
||||||
|
|
|
@ -24,6 +24,7 @@ import DiscussionTopicForm from '../DiscussionTopicForm'
|
||||||
import {DiscussionTopic} from '../../../../graphql/DiscussionTopic'
|
import {DiscussionTopic} from '../../../../graphql/DiscussionTopic'
|
||||||
import {Assignment} from '../../../../graphql/Assignment'
|
import {Assignment} from '../../../../graphql/Assignment'
|
||||||
import {GroupSet} from '../../../../graphql/GroupSet'
|
import {GroupSet} from '../../../../graphql/GroupSet'
|
||||||
|
import {REPLY_TO_TOPIC, REPLY_TO_ENTRY} from '../../../util/constants'
|
||||||
|
|
||||||
jest.mock('@canvas/rce/react/CanvasRce')
|
jest.mock('@canvas/rce/react/CanvasRce')
|
||||||
|
|
||||||
|
@ -455,19 +456,21 @@ describe('DiscussionTopicForm', () => {
|
||||||
})
|
})
|
||||||
it('renders the checkpoints checkbox as selected when there are existing checkpoints', () => {
|
it('renders the checkpoints checkbox as selected when there are existing checkpoints', () => {
|
||||||
const {getByTestId} = setup({
|
const {getByTestId} = setup({
|
||||||
currentDiscussionTopic: DiscussionTopic.mock({assignment: Assignment.mock({hasSubAssignments: true})}),
|
currentDiscussionTopic: DiscussionTopic.mock({
|
||||||
|
assignment: Assignment.mock({hasSubAssignments: true}),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
const checkbox = getByTestId('checkpoints-checkbox')
|
const checkbox = getByTestId('checkpoints-checkbox')
|
||||||
expect(checkbox.checked).toBe(true)
|
expect(checkbox.checked).toBe(true)
|
||||||
})
|
})
|
||||||
describe('Checkpoints Settings', () => {
|
describe('Checkpoints Settings', () => {
|
||||||
let getByTestId, getByLabelText;
|
let getByTestId, getByLabelText
|
||||||
|
|
||||||
const setupCheckpoints = (setupFunction) => {
|
const setupCheckpoints = setupFunction => {
|
||||||
const discussionTopicSetup = setupFunction
|
const discussionTopicSetup = setupFunction
|
||||||
|
|
||||||
getByTestId = discussionTopicSetup.getByTestId;
|
getByTestId = discussionTopicSetup.getByTestId
|
||||||
getByLabelText = discussionTopicSetup.getByLabelText;
|
getByLabelText = discussionTopicSetup.getByLabelText
|
||||||
|
|
||||||
getByLabelText('Graded').click()
|
getByLabelText('Graded').click()
|
||||||
|
|
||||||
|
@ -570,24 +573,28 @@ describe('DiscussionTopicForm', () => {
|
||||||
})
|
})
|
||||||
it('sets the correct checkpoint settings values when there are existing checkpoints', () => {
|
it('sets the correct checkpoint settings values when there are existing checkpoints', () => {
|
||||||
const {getByTestId} = setup({
|
const {getByTestId} = setup({
|
||||||
currentDiscussionTopic: DiscussionTopic.mock({replyToEntryRequiredCount: 5, assignment: Assignment.mock({
|
currentDiscussionTopic: DiscussionTopic.mock({
|
||||||
|
replyToEntryRequiredCount: 5,
|
||||||
|
assignment: Assignment.mock({
|
||||||
hasSubAssignments: true,
|
hasSubAssignments: true,
|
||||||
checkpoints: [{
|
checkpoints: [
|
||||||
"dueAt": null,
|
{
|
||||||
"name": "checkpoint discussion",
|
dueAt: null,
|
||||||
"onlyVisibleToOverrides": false,
|
name: 'checkpoint discussion',
|
||||||
"pointsPossible": 6,
|
onlyVisibleToOverrides: false,
|
||||||
"tag": "reply_to_topic"
|
pointsPossible: 6,
|
||||||
|
tag: REPLY_TO_TOPIC,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"dueAt": null,
|
dueAt: null,
|
||||||
"name": "checkpoint discussion",
|
name: 'checkpoint discussion',
|
||||||
"onlyVisibleToOverrides": false,
|
onlyVisibleToOverrides: false,
|
||||||
"pointsPossible": 7,
|
pointsPossible: 7,
|
||||||
"tag": "reply_to_entry"
|
tag: REPLY_TO_ENTRY,
|
||||||
}
|
},
|
||||||
]}
|
],
|
||||||
)}),
|
}),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
|
|
||||||
const numberInputReplyToTopic = getByTestId('points-possible-input-reply-to-topic')
|
const numberInputReplyToTopic = getByTestId('points-possible-input-reply-to-topic')
|
||||||
|
|
|
@ -63,6 +63,9 @@ export const ASSIGNMENT_OVERRIDE_GRAPHQL_TYPENAMES = {
|
||||||
export const minimumReplyToEntryRequiredCount = 1
|
export const minimumReplyToEntryRequiredCount = 1
|
||||||
export const maximumReplyToEntryRequiredCount = 10
|
export const maximumReplyToEntryRequiredCount = 10
|
||||||
|
|
||||||
|
export const REPLY_TO_TOPIC = 'reply_to_topic'
|
||||||
|
export const REPLY_TO_ENTRY = 'reply_to_entry'
|
||||||
|
|
||||||
export const useShouldShowContent = (
|
export const useShouldShowContent = (
|
||||||
isGraded,
|
isGraded,
|
||||||
isAnnouncement,
|
isAnnouncement,
|
||||||
|
|
|
@ -16,6 +16,8 @@
|
||||||
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
* with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {REPLY_TO_TOPIC, REPLY_TO_ENTRY} from './constants'
|
||||||
|
|
||||||
const prepareOverride = (
|
const prepareOverride = (
|
||||||
overrideDueDate,
|
overrideDueDate,
|
||||||
overrideAvailableUntil,
|
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 = (
|
export const prepareAssignmentPayload = (
|
||||||
isEditing,
|
isEditing,
|
||||||
title,
|
title,
|
||||||
|
@ -165,7 +191,8 @@ export const prepareAssignmentPayload = (
|
||||||
peerReviewsPerStudent,
|
peerReviewsPerStudent,
|
||||||
peerReviewDueDate,
|
peerReviewDueDate,
|
||||||
intraGroupPeerReviews,
|
intraGroupPeerReviews,
|
||||||
masteryPathsOption
|
masteryPathsOption,
|
||||||
|
isCheckpoints
|
||||||
) => {
|
) => {
|
||||||
// Return null immediately if the assignment is not graded
|
// Return null immediately if the assignment is not graded
|
||||||
if (!isGraded) return null
|
if (!isGraded) return null
|
||||||
|
@ -176,10 +203,8 @@ export const prepareAssignmentPayload = (
|
||||||
info.assignedList.includes(defaultEveryoneOption.assetCode) ||
|
info.assignedList.includes(defaultEveryoneOption.assetCode) ||
|
||||||
info.assignedList.includes(defaultEveryoneElseOption.assetCode)
|
info.assignedList.includes(defaultEveryoneElseOption.assetCode)
|
||||||
) || {}
|
) || {}
|
||||||
|
|
||||||
// Common payload properties for graded assignments
|
// Common payload properties for graded assignments
|
||||||
let payload = {
|
let payload = {
|
||||||
pointsPossible,
|
|
||||||
postToSis,
|
postToSis,
|
||||||
gradingType: displayGradeAs,
|
gradingType: displayGradeAs,
|
||||||
assignmentGroupId: assignmentGroup || null,
|
assignmentGroupId: assignmentGroup || null,
|
||||||
|
@ -195,11 +220,19 @@ export const prepareAssignmentPayload = (
|
||||||
defaultEveryoneOption,
|
defaultEveryoneOption,
|
||||||
masteryPathsOption
|
masteryPathsOption
|
||||||
),
|
),
|
||||||
|
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,
|
dueAt: everyoneOverride.dueDate || null,
|
||||||
lockAt: everyoneOverride.availableUntil || null,
|
lockAt: everyoneOverride.availableUntil || null,
|
||||||
unlockAt: everyoneOverride.availableFrom || null,
|
unlockAt: everyoneOverride.availableFrom || null,
|
||||||
onlyVisibleToOverrides: !Object.keys(everyoneOverride).length,
|
}
|
||||||
gradingStandardId: gradingSchemeId || null,
|
|
||||||
}
|
}
|
||||||
// Additional properties for creation of a graded assignment
|
// Additional properties for creation of a graded assignment
|
||||||
if (!isEditing) {
|
if (!isEditing) {
|
||||||
|
|
Loading…
Reference in New Issue