Add checkpoints info to Discussions Index Page
closes VICE-4301 flag=discussion_checkpoints Test plan: - Test passes - Go to Discussions > + Discussion - Fill the title and description - Select the option "Graded" - Select the option "Assign graded checkpoints" - Add points - Click on "Manage Due Dates and Assign To" - Fill in "Reply to Topic Due Date" and "Required Replies Due Date" - click on Apply - Clck "Save and Publish" - Go back to the discussion pages - the newly created Discussion should show the "Reply to topic" and "Required Replies" due dates Change-Id: Icda226183560dcd6bbad9a3c4df312013205b486 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/355284 Reviewed-by: Omar Soto-Fortuño <omar.soto@instructure.com> Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> QA-Review: Dave Wenzlick <david.wenzlick@instructure.com> Product-Review: Sam Garza <sam.garza@instructure.com>
This commit is contained in:
parent
7c8cf65ff4
commit
dc98645819
|
@ -163,7 +163,8 @@ module Api::V1::DiscussionTopics
|
|||
override_dates: opts[:override_dates],
|
||||
include_all_dates: opts[:include_all_dates],
|
||||
exclude_response_fields: excludes,
|
||||
include_overrides: opts[:include_overrides] }.merge(opts[:assignment_opts]))
|
||||
include_overrides: opts[:include_overrides],
|
||||
include_checkpoints: true }.merge(opts[:assignment_opts]))
|
||||
end
|
||||
|
||||
# ignore :include_sections_user_count for non-course contexts like groups
|
||||
|
|
|
@ -18,15 +18,14 @@
|
|||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
require_relative "pages/discussions_index_page"
|
||||
require_relative "../helpers/items_assign_to_tray"
|
||||
require_relative "pages/discussion_page"
|
||||
require_relative "../common"
|
||||
|
||||
describe "discussions index" do
|
||||
include_context "in-process server selenium tests"
|
||||
|
||||
context "as a student" do
|
||||
discussion1_title = "Meaning of life"
|
||||
discussion2_title = "Meaning of the universe"
|
||||
|
||||
before :once do
|
||||
def setup_course_and_students
|
||||
@teacher = user_with_pseudonym(active_user: true)
|
||||
@student = user_with_pseudonym(active_user: true)
|
||||
@account = Account.create(name: "New Account", default_time_zone: "UTC")
|
||||
|
@ -35,7 +34,17 @@ describe "discussions index" do
|
|||
active_course: true)
|
||||
course_with_teacher(user: @teacher, active_course: true, active_enrollment: true)
|
||||
course_with_student(course: @course, active_enrollment: true)
|
||||
@student2 = user_factory(name: "second user", short_name: "second")
|
||||
user_with_pseudonym(user: @student2, active_user: true)
|
||||
@course.enroll_student(@student2, enrollment_state: "active")
|
||||
end
|
||||
|
||||
context "as a student" do
|
||||
discussion1_title = "Meaning of life"
|
||||
discussion2_title = "Meaning of the universe"
|
||||
|
||||
before :once do
|
||||
setup_course_and_students
|
||||
# Discussion attributes: title, message, delayed_post_at, user
|
||||
@discussion1 = @course.discussion_topics.create!(
|
||||
title: discussion1_title,
|
||||
|
@ -80,4 +89,57 @@ describe "discussions index" do
|
|||
expect_new_page_load { DiscussionsIndex.click_add_discussion }
|
||||
end
|
||||
end
|
||||
|
||||
context "discussion checkpoints" do
|
||||
include ItemsAssignToTray
|
||||
|
||||
before :once do
|
||||
Account.default.enable_feature! :discussion_create
|
||||
Account.default.enable_feature! :discussion_checkpoints
|
||||
Account.default.enable_feature! :react_discussions_post
|
||||
setup_course_and_students
|
||||
end
|
||||
|
||||
it "show checkpoint info on the index page", :ignore_js_errors do
|
||||
user_session(@teacher)
|
||||
Discussion.start_new_discussion(@course.id)
|
||||
wait_for_ajaximations
|
||||
Discussion.topic_title_input.send_keys("Test Checkpoint")
|
||||
Discussion.update_discussion_message
|
||||
Discussion.click_graded_checkbox
|
||||
Discussion.click_checkpoints_checkbox
|
||||
Discussion.reply_to_topic_points_possible_input.send_keys("10")
|
||||
Discussion.reply_to_entry_required_count_input.send_keys("1")
|
||||
Discussion.points_possible_reply_to_entry_input.send_keys("10")
|
||||
Discussion.click_assign_to_button
|
||||
next_week = 1.week.from_now
|
||||
half_month = 2.weeks.from_now
|
||||
update_reply_to_topic_date(0, format_date_for_view(next_week))
|
||||
update_reply_to_topic_time(0, "11:59 PM")
|
||||
update_required_replies_date(0, format_date_for_view(next_week))
|
||||
update_required_replies_time(0, "11:59 PM")
|
||||
click_add_assign_to_card
|
||||
select_module_item_assignee(1, @student.name)
|
||||
update_reply_to_topic_date(1, format_date_for_view(half_month))
|
||||
update_reply_to_topic_time(1, "11:59 PM")
|
||||
update_required_replies_date(1, format_date_for_view(half_month))
|
||||
update_required_replies_time(1, "11:59 PM")
|
||||
click_save_button("Apply")
|
||||
Discussion.save_and_publish_button.click
|
||||
# student within everyone
|
||||
user_session(@student2)
|
||||
get "/courses/#{@course.id}/discussion_topics"
|
||||
expect(fj("span:contains('Reply to topic: #{format_date_for_view(next_week)}')")).to be_present
|
||||
expect(fj("span:contains('Required replies (1): #{format_date_for_view(next_week)}')")).to be_present
|
||||
expect("body").not_to contain_jqcss("span:contains('Reply to topic: #{format_date_for_view(half_month)}')")
|
||||
expect("body").not_to contain_jqcss("span:contains('Required replies (1): #{format_date_for_view(half_month)}')")
|
||||
# student in the second assign card
|
||||
user_session(@student)
|
||||
get "/courses/#{@course.id}/discussion_topics"
|
||||
expect(fj("span:contains('Reply to topic: #{format_date_for_view(half_month)}')")).to be_present
|
||||
expect(fj("span:contains('Required replies (1): #{format_date_for_view(half_month)}')")).to be_present
|
||||
expect("body").not_to contain_jqcss("span:contains('Reply to topic: #{format_date_for_view(next_week)}')")
|
||||
expect("body").not_to contain_jqcss("span:contains('Required replies (1): #{format_date_for_view(next_week)}')")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -39,6 +39,10 @@ class Discussion
|
|||
"input[type=checkbox][value='graded']"
|
||||
end
|
||||
|
||||
def checkpoints_checkbox_selector
|
||||
"input[data-testid='checkpoints-checkbox']"
|
||||
end
|
||||
|
||||
def topic_input_selector
|
||||
"input[placeholder='Topic Title']"
|
||||
end
|
||||
|
@ -51,6 +55,18 @@ class Discussion
|
|||
"input[data-testid='points-possible-input']"
|
||||
end
|
||||
|
||||
def reply_to_topic_points_possible_input_selector
|
||||
"input[data-testid='points-possible-input-reply-to-topic']"
|
||||
end
|
||||
|
||||
def reply_to_entry_required_count_input_selector
|
||||
"input[data-testid='reply-to-entry-required-count']"
|
||||
end
|
||||
|
||||
def points_possible_reply_to_entry_input_selector
|
||||
"input[data-testid='points-possible-input-reply-to-entry']"
|
||||
end
|
||||
|
||||
def save_and_publish_button_selector
|
||||
"button[data-testid='save-and-publish-button']"
|
||||
end
|
||||
|
@ -133,6 +149,10 @@ class Discussion
|
|||
f(grade_checkbox_selector)
|
||||
end
|
||||
|
||||
def checkpoints_checkbox
|
||||
f(checkpoints_checkbox_selector)
|
||||
end
|
||||
|
||||
def post_reply_button
|
||||
fj('button:contains("Post Reply")')
|
||||
end
|
||||
|
@ -215,6 +235,18 @@ class Discussion
|
|||
f(points_possible_input_selector)
|
||||
end
|
||||
|
||||
def reply_to_topic_points_possible_input
|
||||
f(reply_to_topic_points_possible_input_selector)
|
||||
end
|
||||
|
||||
def reply_to_entry_required_count_input
|
||||
f(reply_to_entry_required_count_input_selector)
|
||||
end
|
||||
|
||||
def points_possible_reply_to_entry_input
|
||||
f(points_possible_reply_to_entry_input_selector)
|
||||
end
|
||||
|
||||
def save_and_publish_button
|
||||
f(save_and_publish_button_selector)
|
||||
end
|
||||
|
@ -253,6 +285,10 @@ class Discussion
|
|||
force_click_native(grade_checkbox_selector)
|
||||
end
|
||||
|
||||
def click_checkpoints_checkbox
|
||||
force_click_native(checkpoints_checkbox_selector)
|
||||
end
|
||||
|
||||
def click_summarize_button
|
||||
summarize_button.click
|
||||
end
|
||||
|
|
|
@ -111,7 +111,8 @@ const dropTarget = {
|
|||
props.moveCard(dragIndex, hoverIndex)
|
||||
},
|
||||
}
|
||||
|
||||
const REPLY_TO_TOPIC = 'reply_to_topic'
|
||||
const REPLY_TO_ENTRY = 'reply_to_entry'
|
||||
class DiscussionRow extends Component {
|
||||
static propTypes = {
|
||||
canPublish: bool.isRequired,
|
||||
|
@ -795,6 +796,39 @@ class DiscussionRow extends Component {
|
|||
)
|
||||
}
|
||||
|
||||
renderCheckpointInfo = (size, timestampStyleOverride) => {
|
||||
const {assignment} = this.props.discussion
|
||||
let dueDateString = null
|
||||
|
||||
if (assignment && assignment?.checkpoints?.length > 0) {
|
||||
const replyToTopic = assignment.checkpoints.find(e => e.tag === REPLY_TO_TOPIC).due_at
|
||||
const replyToEntry = assignment.checkpoints.find(e => e.tag === REPLY_TO_ENTRY).due_at
|
||||
const noDate = I18n.t('No Due Date')
|
||||
|
||||
dueDateString = I18n.t(
|
||||
' Reply to topic: %{topicDate} Required replies (%{count}): %{entryDate}',
|
||||
{
|
||||
topicDate: replyToTopic ? this.props.dateFormatter(replyToTopic) : noDate,
|
||||
entryDate: replyToEntry ? this.props.dateFormatter(replyToEntry) : noDate,
|
||||
count: this.props.discussion.reply_to_entry_required_count,
|
||||
}
|
||||
)
|
||||
}
|
||||
return (
|
||||
dueDateString && (
|
||||
<Grid.Row>
|
||||
<Grid.Col textAlign="end">
|
||||
<span aria-hidden="true" style={timestampStyleOverride}>
|
||||
<span className="ic-discussion-row__content due-date">
|
||||
<Text size={size}>{dueDateString}</Text>
|
||||
</span>
|
||||
</span>
|
||||
</Grid.Col>
|
||||
</Grid.Row>
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
renderIcon = () => {
|
||||
const accessibleGradedIcon = (isSuccessColor = true) => (
|
||||
<Text
|
||||
|
@ -947,6 +981,7 @@ class DiscussionRow extends Component {
|
|||
</span>
|
||||
</Grid.Col>
|
||||
</Grid.Row>
|
||||
{this.renderCheckpointInfo(timestampTextSize, timestampStyleOverride)}
|
||||
</Grid>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -319,6 +319,66 @@ describe('DiscussionRow', () => {
|
|||
expect(screen.queryByText('To do', {exact: false})).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders checkpoint information', () => {
|
||||
const props = makeProps({
|
||||
discussion: {
|
||||
reply_to_entry_required_count: 2,
|
||||
assignment: {
|
||||
checkpoints: [
|
||||
{
|
||||
tag: 'reply_to_topic',
|
||||
points_possible: 20,
|
||||
due_at: '2024-09-14T05:59:00Z',
|
||||
},
|
||||
{
|
||||
tag: 'reply_to_entry',
|
||||
points_possible: 10,
|
||||
due_at: '2024-09-21T05:59:00Z',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
render(<DiscussionRow {...props} />)
|
||||
expect(screen.queryByText('Reply to topic:', {exact: false})).toBeInTheDocument()
|
||||
expect(screen.queryByText('Required replies (2):', {exact: false})).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText(props.dateFormatter('2024-09-14T05:59:00Z'), {exact: false})
|
||||
).toBeInTheDocument()
|
||||
expect(
|
||||
screen.queryByText(props.dateFormatter('2024-09-21T05:59:00Z'), {exact: false})
|
||||
).toBeInTheDocument()
|
||||
expect(screen.queryByText('No Due Date', {exact: false})).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders checkpoint information without due dates', () => {
|
||||
const props = makeProps({
|
||||
discussion: {
|
||||
reply_to_entry_required_count: 4,
|
||||
assignment: {
|
||||
checkpoints: [
|
||||
{
|
||||
tag: 'reply_to_topic',
|
||||
points_possible: 10,
|
||||
due_at: null,
|
||||
},
|
||||
{
|
||||
tag: 'reply_to_entry',
|
||||
points_possible: 20,
|
||||
due_at: null,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
})
|
||||
render(<DiscussionRow {...props} />)
|
||||
expect(
|
||||
screen.queryByText('Reply to topic: No Due Date Required replies (4): No Due Date', {
|
||||
exact: false,
|
||||
})
|
||||
).toBeInTheDocument()
|
||||
})
|
||||
|
||||
it('renders to do date if ungraded with a to do date', () => {
|
||||
const props = makeProps({
|
||||
discussion: {
|
||||
|
|
Loading…
Reference in New Issue