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:
Roberto Noguera 2024-08-16 10:46:16 -06:00
parent 7c8cf65ff4
commit dc98645819
5 changed files with 205 additions and 11 deletions

View File

@ -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

View File

@ -18,24 +18,33 @@
# 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"
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")
@course = course_factory(course_name: "Aaron 101",
account: @account,
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
@teacher = user_with_pseudonym(active_user: true)
@student = user_with_pseudonym(active_user: true)
@account = Account.create(name: "New Account", default_time_zone: "UTC")
@course = course_factory(course_name: "Aaron 101",
account: @account,
active_course: true)
course_with_teacher(user: @teacher, active_course: true, active_enrollment: true)
course_with_student(course: @course, active_enrollment: true)
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

View File

@ -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

View File

@ -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>

View File

@ -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: {