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],
|
override_dates: opts[:override_dates],
|
||||||
include_all_dates: opts[:include_all_dates],
|
include_all_dates: opts[:include_all_dates],
|
||||||
exclude_response_fields: excludes,
|
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
|
end
|
||||||
|
|
||||||
# ignore :include_sections_user_count for non-course contexts like groups
|
# ignore :include_sections_user_count for non-course contexts like groups
|
||||||
|
|
|
@ -18,24 +18,33 @@
|
||||||
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
require_relative "pages/discussions_index_page"
|
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
|
describe "discussions index" do
|
||||||
include_context "in-process server selenium tests"
|
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
|
context "as a student" do
|
||||||
discussion1_title = "Meaning of life"
|
discussion1_title = "Meaning of life"
|
||||||
discussion2_title = "Meaning of the universe"
|
discussion2_title = "Meaning of the universe"
|
||||||
|
|
||||||
before :once do
|
before :once do
|
||||||
@teacher = user_with_pseudonym(active_user: true)
|
setup_course_and_students
|
||||||
@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)
|
|
||||||
|
|
||||||
# Discussion attributes: title, message, delayed_post_at, user
|
# Discussion attributes: title, message, delayed_post_at, user
|
||||||
@discussion1 = @course.discussion_topics.create!(
|
@discussion1 = @course.discussion_topics.create!(
|
||||||
title: discussion1_title,
|
title: discussion1_title,
|
||||||
|
@ -80,4 +89,57 @@ describe "discussions index" do
|
||||||
expect_new_page_load { DiscussionsIndex.click_add_discussion }
|
expect_new_page_load { DiscussionsIndex.click_add_discussion }
|
||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|
|
@ -39,6 +39,10 @@ class Discussion
|
||||||
"input[type=checkbox][value='graded']"
|
"input[type=checkbox][value='graded']"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def checkpoints_checkbox_selector
|
||||||
|
"input[data-testid='checkpoints-checkbox']"
|
||||||
|
end
|
||||||
|
|
||||||
def topic_input_selector
|
def topic_input_selector
|
||||||
"input[placeholder='Topic Title']"
|
"input[placeholder='Topic Title']"
|
||||||
end
|
end
|
||||||
|
@ -51,6 +55,18 @@ class Discussion
|
||||||
"input[data-testid='points-possible-input']"
|
"input[data-testid='points-possible-input']"
|
||||||
end
|
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
|
def save_and_publish_button_selector
|
||||||
"button[data-testid='save-and-publish-button']"
|
"button[data-testid='save-and-publish-button']"
|
||||||
end
|
end
|
||||||
|
@ -133,6 +149,10 @@ class Discussion
|
||||||
f(grade_checkbox_selector)
|
f(grade_checkbox_selector)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def checkpoints_checkbox
|
||||||
|
f(checkpoints_checkbox_selector)
|
||||||
|
end
|
||||||
|
|
||||||
def post_reply_button
|
def post_reply_button
|
||||||
fj('button:contains("Post Reply")')
|
fj('button:contains("Post Reply")')
|
||||||
end
|
end
|
||||||
|
@ -215,6 +235,18 @@ class Discussion
|
||||||
f(points_possible_input_selector)
|
f(points_possible_input_selector)
|
||||||
end
|
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
|
def save_and_publish_button
|
||||||
f(save_and_publish_button_selector)
|
f(save_and_publish_button_selector)
|
||||||
end
|
end
|
||||||
|
@ -253,6 +285,10 @@ class Discussion
|
||||||
force_click_native(grade_checkbox_selector)
|
force_click_native(grade_checkbox_selector)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def click_checkpoints_checkbox
|
||||||
|
force_click_native(checkpoints_checkbox_selector)
|
||||||
|
end
|
||||||
|
|
||||||
def click_summarize_button
|
def click_summarize_button
|
||||||
summarize_button.click
|
summarize_button.click
|
||||||
end
|
end
|
||||||
|
|
|
@ -111,7 +111,8 @@ const dropTarget = {
|
||||||
props.moveCard(dragIndex, hoverIndex)
|
props.moveCard(dragIndex, hoverIndex)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
const REPLY_TO_TOPIC = 'reply_to_topic'
|
||||||
|
const REPLY_TO_ENTRY = 'reply_to_entry'
|
||||||
class DiscussionRow extends Component {
|
class DiscussionRow extends Component {
|
||||||
static propTypes = {
|
static propTypes = {
|
||||||
canPublish: bool.isRequired,
|
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 = () => {
|
renderIcon = () => {
|
||||||
const accessibleGradedIcon = (isSuccessColor = true) => (
|
const accessibleGradedIcon = (isSuccessColor = true) => (
|
||||||
<Text
|
<Text
|
||||||
|
@ -947,6 +981,7 @@ class DiscussionRow extends Component {
|
||||||
</span>
|
</span>
|
||||||
</Grid.Col>
|
</Grid.Col>
|
||||||
</Grid.Row>
|
</Grid.Row>
|
||||||
|
{this.renderCheckpointInfo(timestampTextSize, timestampStyleOverride)}
|
||||||
</Grid>
|
</Grid>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -319,6 +319,66 @@ describe('DiscussionRow', () => {
|
||||||
expect(screen.queryByText('To do', {exact: false})).not.toBeInTheDocument()
|
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', () => {
|
it('renders to do date if ungraded with a to do date', () => {
|
||||||
const props = makeProps({
|
const props = makeProps({
|
||||||
discussion: {
|
discussion: {
|
||||||
|
|
Loading…
Reference in New Issue