canvas-lms/app/models/discussion_topic.rb

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

1816 lines
68 KiB
Ruby
Raw Normal View History

# frozen_string_literal: true
# Copyright (C) 2011 - present Instructure, Inc.
2011-02-01 09:57:29 +08:00
#
# This file is part of Canvas.
#
# Canvas is free software: you can redistribute it and/or modify it under
# the terms of the GNU Affero General Public License as published by the Free
# Software Foundation, version 3 of the License.
#
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
# details.
#
# You should have received a copy of the GNU Affero General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
#
require "atom"
2011-02-01 09:57:29 +08:00
class DiscussionTopic < ActiveRecord::Base
include Workflow
include SendToStream
include HasContentTags
include CopyAuthorizedLinks
include TextHelper
include HtmlTextHelper
allow using an item in modules more than once closes #8769 An item can be added to multiple modules, or even the same module more than once. This is especially useful for attachment items, but is also useful for allowing multiple paths through a course, with say an assignment in two different modules and the user only has to complete one of the two modules. test plan: For an item in only one module, verify that the module navigation still appears if you go straight to that item's page, without going through the modules page. Add an item to more than one module. If you visit that item from the modules page, you'll see the right nav depending on which instance of the item you clicked on. If you visit the item directly without going through the modules page, you'll see no nav. Lock one instance of the item by adding a prerequisite, but leave the other unlocked. You can still see the item as a student. Lock all instances of the item with prerequisites. The item will now be locked and you can't see it as a student. Add completion requirements to the item, such as a minimum score on a quiz. Make the requirements different -- 3 points in one instance and 5 in the other, for instance. Verify that if you get 3 points on the quiz, one item is marked as completed but the other isn't, as expected. Rename the item. Verify that all instances of it in modules get renamed. Change-Id: I4f1b2f6f033062ec47ac34fe5eb973a950c17b0c Reviewed-on: https://gerrit.instructure.com/11671 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Bracken Mosbacker <bracken@instructure.com>
2012-06-19 06:18:43 +08:00
include ContextModuleItem
include SearchTermHelper
Add WikiPage assignment type refs CYOE-67 Test Plan 0. `rake db:migrate` and compile assets. Enable the conditional release feature flag in course settings. 1. Create a published WikiPage, assignment-free. Ensure it is visible to errybody. 2. With the API, create an assignment with submission type 'wiki_page'. A new page will be created for the assignment. ```sh curl "http://canvas.dev:3000/api/v1/courses/1/assignments" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X POST -d '{"assignment": {"name": "Page Asg", "published":true, \ "submission_types":["wiki_page"]}}' ``` 3. Ensure the assignment is assigned to "Everyone" and the page is visible to all. 4. Assign the assignment to a specific student or section. Ensure the page is visible only to the assigned set. ```sh curl "http://canvas.dev:3000/api/v1/courses/1/assignments/13" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X PUT -d '{"assignment": {"only_visible_to_overrides": true }}' curl "http://canvas.dev:3000/api/v1/courses/1/assignments/13/overrides" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X POST -d '{"assignment_override": {"student_ids":[126]}}' ``` 5. Ensure the DA behavior also applies to WikiPage#show and update and edit. 6. Ensure API behavior matches docs for Assignment and Page endpoints. 7. Ensure no regressions in non-page assignments, with and without CR feature flag. Ensure no regressions in discussion topics and announcements, including assigned discussions. Change-Id: Ie53c6ffa4e4533cedd54f10aee3ba0b8a27aa4d5 Reviewed-on: https://gerrit.instructure.com/75139 Reviewed-by: Matt Berns <mberns@instructure.com> Tested-by: Jenkins QA-Review: Michael Hargiss <mhargiss@instructure.com> Product-Review: Christian Prescott <cprescott@instructure.com>
2016-04-08 00:37:16 +08:00
include Submittable
Planner API Closes FALCOR-183 Closes FALCOR-184 Closes FALCOR-255 Closes FALCOR-186 Closes FALCOR-187 Closes FALCOR-188 Closes FALCOR-189 Closes FALCOR-147 Closes FALCOR-148 Test Plan: * As a student in an account with the Student Planner feature flag enabled * In a course with multiple upcoming due assignments in the current and following weeks * Test that the following API endpoints provide you with the data described: get '/api/v1/planner/items' * Should return a list matching that of the todo list endpoint, while adding an additional key named `visible_in_planner` get '/api/v1/planner/overrides' * Should return a list of previously created PlannerOverrides get '/api/v1/planner/overrides/:override_id' * Should return the specific override for the passed in id put '/api/v1/planner/overrides/:override_id' * Should update an existing override's `visible` value to match what was passed in the request. No other values should be updated post '/api/v1/planner/overrides' * Should create a new PlannerOverride with the specified `plannable_type`, `plannable_id`, and `visible` values. `user_id` should match that of the user making the request delete '/api/v1/planner/overrides/:override_id' * Should set the PlannerOverride's `deleted_at` to when the request was made, as well as updating the `workflow_state` to `deleted` Change-Id: I03890a525f8201a8df1d2f1290cdcd549ba548d7 Reviewed-on: https://gerrit.instructure.com/107495 Tested-by: Jenkins Reviewed-by: Steven Burnett <sburnett@instructure.com> QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com> Product-Review: Dan Minkevitch <dan@instructure.com>
2017-05-11 12:40:45 +08:00
include Plannable
include MasterCourses::Restrictor
include DuplicatingObjects
include LockedFor
restrict_columns :content, [:title, :message]
restrict_columns :settings, %i[require_initial_post
discussion_type
assignment_id
pinned
locked
allow_rating
only_graders_can_rate
sort_by_rating
group_category_id]
restrict_columns :state, [:workflow_state]
fix blueprint sync for discussions locked in the past test plan: - have a blueprint/associated course set up - create an ungraded discussion topic in the blueprint and set its "available until" date in the past - perform a sync - change the "available until" date in the blueprint to be in the future - perform another sync - the discussion in the associated course should be updated (no sync exception) extra credit: - do the above, but with an available-until date in the near future. perform the sync before the discussion locks. wait until after it locks, and note that the locking has happened in the associated course. do not manually edit the discussion in the associated course. change the date in the blueprint to be in the future, perform a sync, and the discussion in the associated course should be unlocked again bonus: - manually edit the discussion in the associated course and change the availability dates - also change the blueprint dates and perform a sync - the changes in the associated course should _not_ be overwritten (sync exception) - lock availability dates in the blueprint discussion and make another edit to it - perform a sync and the changes _should_ be overwritten flag = none fixes LS-1641 Change-Id: Id105f6d7e526c11f984fb786e66491b4862e3cfb Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/254794 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Ed Schiebel <eschiebel@instructure.com> Reviewed-by: Mysti Lilla <mysti@instructure.com> QA-Review: Nate Armstrong <narmstrong@instructure.com> Product-Review: Jeremy Stanley <jeremy@instructure.com>
2020-12-10 12:40:39 +08:00
restrict_columns :availability_dates, [:delayed_post_at, :lock_at]
restrict_assignment_columns
BookmarkedCollection for Planner API Closes FALCOR-325 Fixes FALCOR-361 Test Plan Date Range Testing: * With "Student Planner" enabled * As a student * Make a GET request to /api/v1/planner/items with ?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD&filter=new_activity * The response should only contain results with new activity between those time ranges (test with dates and dates with times) Pagination Testing: * With "Student Planner" enabled * As a student * Make a GET request to /api/v1/planner/items with some combination of start_date, end_date, and filter * Include a a `per_page` param * The response should only contain only `per_page` number of results * The response should contain a `Link` header with links to the current, next, and previous pages (you should see a URL with a `page=bookmark:[bunch of characters]`) * Copy the `page` param from the `Link` header for the next page of results * Make a request with the newly copied `page=bookmark:[stuff]` parameter added to the request * You should get the next page of results in the response * The previous and next URLs in the `Link` response header should return the previous and next page of results Regression Testing: * Full regression test of "New Student Planner" Change-Id: Ie6352aec71f5de3c60981aaeee65119cc43ed6f7 Reviewed-on: https://gerrit.instructure.com/114848 Reviewed-by: Steven Burnett <sburnett@instructure.com> Tested-by: Jenkins QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com> Product-Review: Dan Minkevitch <dan@instructure.com>
2017-06-09 10:52:02 +08:00
attr_accessor :user_has_posted, :saved_by, :total_root_discussion_entries
module DiscussionTypes
SIDE_COMMENT = "side_comment"
THREADED = "threaded"
FLAT = "flat"
TYPES = DiscussionTypes.constants.map { |c| DiscussionTypes.const_get(c) }
end
2011-02-01 09:57:29 +08:00
module Errors
class LockBeforeDueDate < StandardError; end
end
attr_readonly :context_id, :context_type, :user_id, :anonymous_state, :is_anonymous_author
has_many :discussion_entries, -> { order(:created_at) }, dependent: :destroy, inverse_of: :discussion_topic
has_many :discussion_entry_drafts, dependent: :destroy, inverse_of: :discussion_topic
has_many :rated_discussion_entries,
lambda {
order(
Arel.sql("COALESCE(parent_id, 0)"), Arel.sql("COALESCE(rating_sum, 0) DESC"), :created_at
)
},
class_name: "DiscussionEntry"
has_many :root_discussion_entries, -> { preload(:user).where("discussion_entries.parent_id IS NULL AND discussion_entries.workflow_state<>'deleted'") }, class_name: "DiscussionEntry"
2011-02-01 09:57:29 +08:00
has_one :external_feed_entry, as: :asset
belongs_to :root_account, class_name: "Account"
2011-02-01 09:57:29 +08:00
belongs_to :external_feed
belongs_to :context, polymorphic: [:course, :group]
2011-02-01 09:57:29 +08:00
belongs_to :attachment
belongs_to :editor, class_name: "User"
belongs_to :root_topic, class_name: "DiscussionTopic"
belongs_to :group_category
has_many :checkpoint_assignments, through: :assignment
2011-02-01 09:57:29 +08:00
has_many :child_topics, class_name: "DiscussionTopic", foreign_key: :root_topic_id, dependent: :destroy
has_many :discussion_topic_participants, dependent: :destroy
has_many :discussion_entry_participants, through: :discussion_entries
has_many :discussion_topic_section_visibilities,
lambda {
where("discussion_topic_section_visibilities.workflow_state<>'deleted'")
},
inverse_of: :discussion_topic,
dependent: :destroy
add ability to create section specific anouncement fixes COMMS-546 Test Plan: - Turn on the section specific announcement feature flag located on your account - navigate to the new announcements page (http://localhost:3000/courses/{course_id}/discussion_topics/new?is_announcement=true) - notice the section drop down - add some sections to the drop down - save your announcement and notice it works - click the edit button on your announcement - notice the sections you added are still there - go to your rails console - do `DiscussionTopicSectionVisibility.all.count` - notice it matches the amount of sections you have added to your announcement - remove an section from the announcement and save - in your rails console do DiscussionTopicSectionVisibility.all.map(&:workflow_state) - notice you have one deleted now - click edit again - notice the section list you selected is correct - select "all Sections" - find your announcement in the console (Announcement.last or DiscussionTopic.last usually) - check that it is section_specific (Announcement.last.is_section_specific) - notice that it is false - go back edit one more time - remove all sections from drop down and try to save - notice you can't Change-Id: Ie72c598e91b7683bb922a54159dd97fcd10a9eaa Reviewed-on: https://gerrit.instructure.com/135261 Tested-by: Jenkins Reviewed-by: Felix Milea-Ciobanu <fmileaciobanu@instructure.com> Reviewed-by: Aaron Kc Hsu <ahsu@instructure.com> Reviewed-by: Venk Natarajan <vnatarajan@instructure.com> QA-Review: Landon Gilbert-Bland <lbland@instructure.com> Product-Review: Matt Goodwin <mattg@instructure.com>
2017-12-12 05:22:03 +08:00
has_many :course_sections, through: :discussion_topic_section_visibilities, dependent: :destroy
2011-02-01 09:57:29 +08:00
belongs_to :user
has_one :master_content_tag, class_name: "MasterCourses::MasterContentTag", inverse_of: :discussion_topic
validates_associated :discussion_topic_section_visibilities
validates :context_id, :context_type, presence: true
validates :discussion_type, inclusion: { in: DiscussionTypes::TYPES }
validates :message, length: { maximum: maximum_long_text_length, allow_blank: true }
validates :title, length: { maximum: maximum_string_length, allow_nil: true }
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
validate :validate_draft_state_change, if: :workflow_state_changed?
Create a table for section specific announcements. This creates a column in discussion_topics called "is_section_specific" and also creates a table for storing which sections go to which announcements. Right now, as long as the section_specific_announcements feature is disabled, it will not be possible to use rails to set this new column to true, or add stuff to the new table, so these tables should be frozen to empty. Closes COMMS-550 Closes COMMS-551 Test Plan: * Have a course with sections and discussion topics in it. * Run "bundle exec rake db:migrate:up VERSION=20171208202812" * Run "bundle exec rake db:migrate:up VERSION=20171208202825" * "rails dbconsole" * Verify that "is_section_specific" is present in the discussion_topics table and that it cannot be null. * Verify that "is_section_specific" has an explicit false value in each row. * Run "bundle exec rake db:migrate:down VERSION=20171208202825" * Run "bundle exec rake db:migrate:down VERSION=20171208202812" * (The order in which you run these migrations matters) * Needless to say, the above stuff should all succeed. * Specs pass. * Make sure that, if the feature is disabled, we can't set the is_section_specific column to true and we can't put stuff in the new table. Change-Id: I2b6e75872a1380a481d2ad15fca3d7e1b257a542 Reviewed-on: https://gerrit.instructure.com/135021 Reviewed-by: Cody Cutrer <cody@instructure.com> Reviewed-by: Steven Burnett <sburnett@instructure.com> Tested-by: Jenkins QA-Review: Felix Milea-Ciobanu <fmileaciobanu@instructure.com> Product-Review: Venk Natarajan <vnatarajan@instructure.com>
2017-12-08 07:51:21 +08:00
validate :section_specific_topics_must_have_sections
validate :only_course_topics_can_be_section_specific
Disallows graded or group section specific discussions Graded or group discussions can be assigned to people that are not part of a given section, which could cause all sorts of headaches if that was set with section specific announcements. Instead, this makes graded or group discussions mutually exclusive with section specific discussions, both for the API in the backend and for selecting the options in the frontend. Fixes COMMS-714 Test Plan: - Make sure the section specific discussions feature flag is enabled - Attempt to create a graded section specific discussion and a group section specific discussion through the api and notice that it returns a 4xx error. Here are some tips you could use for doing this: * Create a course with some groups and sections. Make note of the course id, group ids, and section ids. * Install httpie if it is not already * Create an access token for your teacher/admin by going to http://127.0.0.1:3000/profile/settings and clicking the 'New Access Token' button. * In a terminal, save this token so that it can be easily used by multiple tests bellow: export TOKEN=<token_string> * Use the following commands to test the API, substituting the ids for the ones you made a note of above. Make sure they return a 4xx error and have a related error message: http POST :3000/api/v1/courses/1/discussion_topics Authorization:"Bearer $TOKEN" is_announcement:=false specific_sections="4,5" group_category_id="4" echo '{"is_announcement": false, "specific_sections": "4,5", "set_assignment": "1", "assignment": {"points_possible": 1, "grading_type": "points"}}' | http POST :3000/api/v1/courses/1/discussion_topics Authorization:"Bearer $TOKEN" - From create new discussions page, notice that if you change the section specific stuff to not be `all sections`, the graded and group options become disabled. - From create new discussions page, notice that if you check the graded or groups checkbox, the section specific field becomes disabled. - Notice that if you load an announcement that already has section specific, graded, or grouped saved, it will probably have the mutually exclusive fields disabled - Notice that everything properly says that it is disabled when using a screen reader. Also note that there is some funkyness with selecting elements with the autocomplete component, that is already known and being worked on by the instui team, and can be safely ignored while QAing this. - Make sure that you are not allowed to create a discussion if nothing is selected for the section specific input, and that it properly pops up an error message saying that 'You must input a section' Change-Id: Ic63e733e7411116eb3fd637c81ad5cb1a63813ed Reviewed-on: https://gerrit.instructure.com/139319 Reviewed-by: Venk Natarajan <vnatarajan@instructure.com> Reviewed-by: Felix Milea-Ciobanu <fmileaciobanu@instructure.com> QA-Review: Steven Burnett <sburnett@instructure.com> Product-Review: Landon Gilbert-Bland <lbland@instructure.com> Tested-by: Jenkins
2018-01-27 00:28:18 +08:00
validate :assignments_cannot_be_section_specific
validate :course_group_discussion_cannot_be_section_specific
sanitize_field :message, CanvasSanitize::SANITIZE
2011-02-01 09:57:29 +08:00
copy_authorized_links(:message) { [context, nil] }
acts_as_list scope: { context: self, pinned: true }
2011-02-01 09:57:29 +08:00
before_create :initialize_last_reply_at
before_create :set_root_account_id
2011-02-01 09:57:29 +08:00
before_save :default_values
added auto lock discussions on specified date fixes CNVS-4106 test steps: - go to create a discussion topic and ensure that you can toggle between the 'Graded' and non-graded availability dates. If the Graded is not checked, you should see the Available From/Until. - create a discussion topic with a past from and past until. ensure that the discussion is locked for a student - create a discussion topic with a future from and future until. ensure the discussion cannot be seen by the student - update the above created discussion topic and switch it to be graded and set a future due, from, until date. ensure that the student cannot see the discussion. - edit the discussion and ensure that the graded checkbox is checked and the group assignment fields are showing - update the assignment with a past due, from, until and make sure the discussion is locked for the student. - do all above with current dates (from in past, until in future) and make sure the student can see and reply to them. - create an announcement and ensure that the functionality and forms are the same as before - verify that you are able to lock and unlock discussion topics on the show view by using the gear drop-down Change-Id: I99c9d54763fe3a74aa8a4bb37c22f09d4765d41e Reviewed-on: https://gerrit.instructure.com/20339 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Reviewed-by: Zach Pendleton <zachp@instructure.com> QA-Review: Marc LeGendre <marc@instructure.com> Product-Review: Marc LeGendre <marc@instructure.com>
2013-05-03 07:39:26 +08:00
before_save :set_schedule_delayed_transitions
2011-02-01 09:57:29 +08:00
after_save :update_assignment
after_save :update_subtopics
after_save :touch_context
added auto lock discussions on specified date fixes CNVS-4106 test steps: - go to create a discussion topic and ensure that you can toggle between the 'Graded' and non-graded availability dates. If the Graded is not checked, you should see the Available From/Until. - create a discussion topic with a past from and past until. ensure that the discussion is locked for a student - create a discussion topic with a future from and future until. ensure the discussion cannot be seen by the student - update the above created discussion topic and switch it to be graded and set a future due, from, until date. ensure that the student cannot see the discussion. - edit the discussion and ensure that the graded checkbox is checked and the group assignment fields are showing - update the assignment with a past due, from, until and make sure the discussion is locked for the student. - do all above with current dates (from in past, until in future) and make sure the student can see and reply to them. - create an announcement and ensure that the functionality and forms are the same as before - verify that you are able to lock and unlock discussion topics on the show view by using the gear drop-down Change-Id: I99c9d54763fe3a74aa8a4bb37c22f09d4765d41e Reviewed-on: https://gerrit.instructure.com/20339 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Reviewed-by: Zach Pendleton <zachp@instructure.com> QA-Review: Marc LeGendre <marc@instructure.com> Product-Review: Marc LeGendre <marc@instructure.com>
2013-05-03 07:39:26 +08:00
after_save :schedule_delayed_transitions
after_save :update_materialized_view_if_changed
after_save :recalculate_progressions_if_sections_changed
after_save :sync_attachment_with_publish_state
after_update :clear_non_applicable_stream_items
after_create :create_participant
after_create :create_materialized_view
Create a table for section specific announcements. This creates a column in discussion_topics called "is_section_specific" and also creates a table for storing which sections go to which announcements. Right now, as long as the section_specific_announcements feature is disabled, it will not be possible to use rails to set this new column to true, or add stuff to the new table, so these tables should be frozen to empty. Closes COMMS-550 Closes COMMS-551 Test Plan: * Have a course with sections and discussion topics in it. * Run "bundle exec rake db:migrate:up VERSION=20171208202812" * Run "bundle exec rake db:migrate:up VERSION=20171208202825" * "rails dbconsole" * Verify that "is_section_specific" is present in the discussion_topics table and that it cannot be null. * Verify that "is_section_specific" has an explicit false value in each row. * Run "bundle exec rake db:migrate:down VERSION=20171208202825" * Run "bundle exec rake db:migrate:down VERSION=20171208202812" * (The order in which you run these migrations matters) * Needless to say, the above stuff should all succeed. * Specs pass. * Make sure that, if the feature is disabled, we can't set the is_section_specific column to true and we can't put stuff in the new table. Change-Id: I2b6e75872a1380a481d2ad15fca3d7e1b257a542 Reviewed-on: https://gerrit.instructure.com/135021 Reviewed-by: Cody Cutrer <cody@instructure.com> Reviewed-by: Steven Burnett <sburnett@instructure.com> Tested-by: Jenkins QA-Review: Felix Milea-Ciobanu <fmileaciobanu@instructure.com> Product-Review: Venk Natarajan <vnatarajan@instructure.com>
2017-12-08 07:51:21 +08:00
def section_specific_topics_must_have_sections
if !deleted? && is_section_specific && discussion_topic_section_visibilities.none?(&:active?)
Create a table for section specific announcements. This creates a column in discussion_topics called "is_section_specific" and also creates a table for storing which sections go to which announcements. Right now, as long as the section_specific_announcements feature is disabled, it will not be possible to use rails to set this new column to true, or add stuff to the new table, so these tables should be frozen to empty. Closes COMMS-550 Closes COMMS-551 Test Plan: * Have a course with sections and discussion topics in it. * Run "bundle exec rake db:migrate:up VERSION=20171208202812" * Run "bundle exec rake db:migrate:up VERSION=20171208202825" * "rails dbconsole" * Verify that "is_section_specific" is present in the discussion_topics table and that it cannot be null. * Verify that "is_section_specific" has an explicit false value in each row. * Run "bundle exec rake db:migrate:down VERSION=20171208202825" * Run "bundle exec rake db:migrate:down VERSION=20171208202812" * (The order in which you run these migrations matters) * Needless to say, the above stuff should all succeed. * Specs pass. * Make sure that, if the feature is disabled, we can't set the is_section_specific column to true and we can't put stuff in the new table. Change-Id: I2b6e75872a1380a481d2ad15fca3d7e1b257a542 Reviewed-on: https://gerrit.instructure.com/135021 Reviewed-by: Cody Cutrer <cody@instructure.com> Reviewed-by: Steven Burnett <sburnett@instructure.com> Tested-by: Jenkins QA-Review: Felix Milea-Ciobanu <fmileaciobanu@instructure.com> Product-Review: Venk Natarajan <vnatarajan@instructure.com>
2017-12-08 07:51:21 +08:00
errors.add(:is_section_specific, t("Section specific topics must have sections"))
else
true
end
end
def only_course_topics_can_be_section_specific
if is_section_specific && !(context.is_a? Course)
errors.add(:is_section_specific, t("Only course announcements and discussions can be section-specific"))
else
true
end
end
Disallows graded or group section specific discussions Graded or group discussions can be assigned to people that are not part of a given section, which could cause all sorts of headaches if that was set with section specific announcements. Instead, this makes graded or group discussions mutually exclusive with section specific discussions, both for the API in the backend and for selecting the options in the frontend. Fixes COMMS-714 Test Plan: - Make sure the section specific discussions feature flag is enabled - Attempt to create a graded section specific discussion and a group section specific discussion through the api and notice that it returns a 4xx error. Here are some tips you could use for doing this: * Create a course with some groups and sections. Make note of the course id, group ids, and section ids. * Install httpie if it is not already * Create an access token for your teacher/admin by going to http://127.0.0.1:3000/profile/settings and clicking the 'New Access Token' button. * In a terminal, save this token so that it can be easily used by multiple tests bellow: export TOKEN=<token_string> * Use the following commands to test the API, substituting the ids for the ones you made a note of above. Make sure they return a 4xx error and have a related error message: http POST :3000/api/v1/courses/1/discussion_topics Authorization:"Bearer $TOKEN" is_announcement:=false specific_sections="4,5" group_category_id="4" echo '{"is_announcement": false, "specific_sections": "4,5", "set_assignment": "1", "assignment": {"points_possible": 1, "grading_type": "points"}}' | http POST :3000/api/v1/courses/1/discussion_topics Authorization:"Bearer $TOKEN" - From create new discussions page, notice that if you change the section specific stuff to not be `all sections`, the graded and group options become disabled. - From create new discussions page, notice that if you check the graded or groups checkbox, the section specific field becomes disabled. - Notice that if you load an announcement that already has section specific, graded, or grouped saved, it will probably have the mutually exclusive fields disabled - Notice that everything properly says that it is disabled when using a screen reader. Also note that there is some funkyness with selecting elements with the autocomplete component, that is already known and being worked on by the instui team, and can be safely ignored while QAing this. - Make sure that you are not allowed to create a discussion if nothing is selected for the section specific input, and that it properly pops up an error message saying that 'You must input a section' Change-Id: Ic63e733e7411116eb3fd637c81ad5cb1a63813ed Reviewed-on: https://gerrit.instructure.com/139319 Reviewed-by: Venk Natarajan <vnatarajan@instructure.com> Reviewed-by: Felix Milea-Ciobanu <fmileaciobanu@instructure.com> QA-Review: Steven Burnett <sburnett@instructure.com> Product-Review: Landon Gilbert-Bland <lbland@instructure.com> Tested-by: Jenkins
2018-01-27 00:28:18 +08:00
def assignments_cannot_be_section_specific
if is_section_specific && assignment
errors.add(:is_section_specific, t("Discussion assignments cannot be section-specific"))
else
true
end
end
def course_group_discussion_cannot_be_section_specific
if is_section_specific && has_group_category?
errors.add(:is_section_specific, t("Discussions with groups cannot be section-specific"))
else
true
end
end
def sections_for(user)
return unless is_section_specific?
unlocked_teacher = context.enrollments.active.instructor
.where(limit_privileges_to_course_section: false, user:)
if unlocked_teacher.count > 0
CourseSection.where(id: DiscussionTopicSectionVisibility.active
.where(discussion_topic_id: id)
.select("discussion_topic_section_visibilities.course_section_id"))
else
CourseSection.where(id: DiscussionTopicSectionVisibility.active.where(discussion_topic_id: id)
.where(Enrollment.active_or_pending
.where(user_id: user)
.where("enrollments.course_section_id = discussion_topic_section_visibilities.course_section_id")
.arel.exists)
.select("discussion_topic_section_visibilities.course_section_id"))
end
end
def address_book_context_for(user)
if is_section_specific?
sections_for(user)
else
context
end
end
def threaded=(v)
self.discussion_type = Canvas::Plugin.value_to_boolean(v) ? DiscussionTypes::THREADED : DiscussionTypes::SIDE_COMMENT
end
def threaded?
discussion_type == DiscussionTypes::THREADED || context.feature_enabled?("react_discussions_post")
end
alias_method :threaded, :threaded?
def discussion_type
read_attribute(:discussion_type) || DiscussionTypes::SIDE_COMMENT
end
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
def validate_draft_state_change
old_draft_state, new_draft_state = changes["workflow_state"]
return if old_draft_state == new_draft_state
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
if new_draft_state == "unpublished" && !can_unpublish?
errors.add :workflow_state, I18n.t("#discussion_topics.error_draft_state_with_posts",
"This topic cannot be set to draft state because it contains posts.")
end
end
2011-02-01 09:57:29 +08:00
def default_values
self.context_code = "#{context_type.underscore}_#{context_id}"
if title.blank?
self.title = t("#discussion_topic.default_title", "No Title")
end
d_type = read_attribute(:discussion_type)
d_type ||= context.feature_enabled?("react_discussions_post") ? DiscussionTypes::THREADED : DiscussionTypes::SIDE_COMMENT
self.discussion_type = d_type
2011-02-01 09:57:29 +08:00
@content_changed = message_changed? || title_changed?
Add WikiPage assignment type refs CYOE-67 Test Plan 0. `rake db:migrate` and compile assets. Enable the conditional release feature flag in course settings. 1. Create a published WikiPage, assignment-free. Ensure it is visible to errybody. 2. With the API, create an assignment with submission type 'wiki_page'. A new page will be created for the assignment. ```sh curl "http://canvas.dev:3000/api/v1/courses/1/assignments" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X POST -d '{"assignment": {"name": "Page Asg", "published":true, \ "submission_types":["wiki_page"]}}' ``` 3. Ensure the assignment is assigned to "Everyone" and the page is visible to all. 4. Assign the assignment to a specific student or section. Ensure the page is visible only to the assigned set. ```sh curl "http://canvas.dev:3000/api/v1/courses/1/assignments/13" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X PUT -d '{"assignment": {"only_visible_to_overrides": true }}' curl "http://canvas.dev:3000/api/v1/courses/1/assignments/13/overrides" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X POST -d '{"assignment_override": {"student_ids":[126]}}' ``` 5. Ensure the DA behavior also applies to WikiPage#show and update and edit. 6. Ensure API behavior matches docs for Assignment and Page endpoints. 7. Ensure no regressions in non-page assignments, with and without CR feature flag. Ensure no regressions in discussion topics and announcements, including assigned discussions. Change-Id: Ie53c6ffa4e4533cedd54f10aee3ba0b8a27aa4d5 Reviewed-on: https://gerrit.instructure.com/75139 Reviewed-by: Matt Berns <mberns@instructure.com> Tested-by: Jenkins QA-Review: Michael Hargiss <mhargiss@instructure.com> Product-Review: Christian Prescott <cprescott@instructure.com>
2016-04-08 00:37:16 +08:00
default_submission_values
if has_group_category?
self.subtopics_refreshed_at ||= Time.zone.parse("Jan 1 2000")
2011-02-01 09:57:29 +08:00
end
self.lock_at = CanvasTime.fancy_midnight(lock_at&.in_time_zone(context.time_zone))
%i[
could_be_locked
podcast_enabled
podcast_has_student_posts
require_initial_post
pinned
locked
allow_rating
only_graders_can_rate
sort_by_rating
].each { |attr| self[attr] = false if self[attr].nil? }
2011-02-01 09:57:29 +08:00
end
protected :default_values
def has_group_category?
!!group_category_id
end
added auto lock discussions on specified date fixes CNVS-4106 test steps: - go to create a discussion topic and ensure that you can toggle between the 'Graded' and non-graded availability dates. If the Graded is not checked, you should see the Available From/Until. - create a discussion topic with a past from and past until. ensure that the discussion is locked for a student - create a discussion topic with a future from and future until. ensure the discussion cannot be seen by the student - update the above created discussion topic and switch it to be graded and set a future due, from, until date. ensure that the student cannot see the discussion. - edit the discussion and ensure that the graded checkbox is checked and the group assignment fields are showing - update the assignment with a past due, from, until and make sure the discussion is locked for the student. - do all above with current dates (from in past, until in future) and make sure the student can see and reply to them. - create an announcement and ensure that the functionality and forms are the same as before - verify that you are able to lock and unlock discussion topics on the show view by using the gear drop-down Change-Id: I99c9d54763fe3a74aa8a4bb37c22f09d4765d41e Reviewed-on: https://gerrit.instructure.com/20339 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Reviewed-by: Zach Pendleton <zachp@instructure.com> QA-Review: Marc LeGendre <marc@instructure.com> Product-Review: Marc LeGendre <marc@instructure.com>
2013-05-03 07:39:26 +08:00
def set_schedule_delayed_transitions
@delayed_post_at_changed = delayed_post_at_changed?
if delayed_post_at? && @delayed_post_at_changed
@should_schedule_delayed_post = true
self.workflow_state = "post_delayed" if [:migration, :after_migration].include?(saved_by) && delayed_post_at > Time.now
end
if lock_at && lock_at_changed?
@should_schedule_lock_at = true
self.locked = false if [:migration, :after_migration].include?(saved_by) && lock_at > Time.now
end
2011-02-01 09:57:29 +08:00
true
end
def update_materialized_view_if_changed
if saved_change_to_sort_by_rating?
update_materialized_view
end
end
attr_writer :sections_changed
def recalculate_progressions_if_sections_changed
# either changed sections or undid section specificness
return unless is_section_specific? ? @sections_changed : is_section_specific_before_last_save
self.class.connection.after_transaction_commit do
if context_module_tags.preload(:context_module).exists?
context_module_tags.map(&:context_module).uniq.each do |cm|
cm.invalidate_progressions
cm.touch
end
end
end
end
added auto lock discussions on specified date fixes CNVS-4106 test steps: - go to create a discussion topic and ensure that you can toggle between the 'Graded' and non-graded availability dates. If the Graded is not checked, you should see the Available From/Until. - create a discussion topic with a past from and past until. ensure that the discussion is locked for a student - create a discussion topic with a future from and future until. ensure the discussion cannot be seen by the student - update the above created discussion topic and switch it to be graded and set a future due, from, until date. ensure that the student cannot see the discussion. - edit the discussion and ensure that the graded checkbox is checked and the group assignment fields are showing - update the assignment with a past due, from, until and make sure the discussion is locked for the student. - do all above with current dates (from in past, until in future) and make sure the student can see and reply to them. - create an announcement and ensure that the functionality and forms are the same as before - verify that you are able to lock and unlock discussion topics on the show view by using the gear drop-down Change-Id: I99c9d54763fe3a74aa8a4bb37c22f09d4765d41e Reviewed-on: https://gerrit.instructure.com/20339 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Reviewed-by: Zach Pendleton <zachp@instructure.com> QA-Review: Marc LeGendre <marc@instructure.com> Product-Review: Marc LeGendre <marc@instructure.com>
2013-05-03 07:39:26 +08:00
def schedule_delayed_transitions
return if saved_by == :migration
fix blueprint sync for discussions locked in the past test plan: - have a blueprint/associated course set up - create an ungraded discussion topic in the blueprint and set its "available until" date in the past - perform a sync - change the "available until" date in the blueprint to be in the future - perform another sync - the discussion in the associated course should be updated (no sync exception) extra credit: - do the above, but with an available-until date in the near future. perform the sync before the discussion locks. wait until after it locks, and note that the locking has happened in the associated course. do not manually edit the discussion in the associated course. change the date in the blueprint to be in the future, perform a sync, and the discussion in the associated course should be unlocked again bonus: - manually edit the discussion in the associated course and change the availability dates - also change the blueprint dates and perform a sync - the changes in the associated course should _not_ be overwritten (sync exception) - lock availability dates in the blueprint discussion and make another edit to it - perform a sync and the changes _should_ be overwritten flag = none fixes LS-1641 Change-Id: Id105f6d7e526c11f984fb786e66491b4862e3cfb Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/254794 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Ed Schiebel <eschiebel@instructure.com> Reviewed-by: Mysti Lilla <mysti@instructure.com> QA-Review: Nate Armstrong <narmstrong@instructure.com> Product-Review: Jeremy Stanley <jeremy@instructure.com>
2020-12-10 12:40:39 +08:00
bp = true if @importing_migration&.migration_type == "master_course_import"
delay(run_at: delayed_post_at).update_based_on_date(for_blueprint: bp) if @should_schedule_delayed_post
delay(run_at: lock_at).update_based_on_date(for_blueprint: bp) if @should_schedule_lock_at
# need to clear these in case we do a save whilst saving (e.g.
# Announcement#respect_context_lock_rules), so as to avoid the dreaded
# double delayed job ಠ_ಠ
@should_schedule_delayed_post = nil
@should_schedule_lock_at = nil
2011-02-01 09:57:29 +08:00
end
def sync_attachment_with_publish_state
if (saved_change_to_workflow_state? || saved_change_to_locked? || saved_change_to_attachment_id?) &&
attachment && !attachment.hidden? # if it's already hidden leave alone
locked = !!(unpublished? || not_available_yet? || not_available_anymore?)
attachment.update_attribute(:locked, locked)
end
end
2011-02-01 09:57:29 +08:00
def update_subtopics
if !deleted? && (has_group_category? || !!group_category_id_before_last_save)
delay_if_production(singleton: "refresh_subtopics_#{global_id}").refresh_subtopics
2011-02-01 09:57:29 +08:00
end
end
2011-02-01 09:57:29 +08:00
def refresh_subtopics
sub_topics = []
category = group_category
if category && root_topic_id.blank? && !deleted?
category.groups.active.order(:id).each do |group|
sub_topics << ensure_child_topic_for(group)
end
end
shard.activate do
# delete any lingering child topics
DiscussionTopic.where(root_topic_id: self).where.not(id: sub_topics).update_all(workflow_state: "deleted")
end
end
def ensure_child_topic_for(group)
group.shard.activate do
DiscussionTopic.unique_constraint_retry do
topic = DiscussionTopic.where(context_id: group, context_type: "Group", root_topic_id: self).first
topic ||= group.discussion_topics.build { |dt| dt.root_topic = self }
topic.message = message
topic.title = CanvasTextHelper.truncate_text("#{title} - #{group.name}", { max_length: 250 }) # because of course people do this
topic.assignment_id = assignment_id
topic.attachment_id = attachment_id
topic.group_category_id = group_category_id
topic.user_id = user_id
topic.discussion_type = discussion_type
topic.workflow_state = workflow_state
topic.allow_rating = allow_rating
topic.only_graders_can_rate = only_graders_can_rate
topic.sort_by_rating = sort_by_rating
topic.save if topic.changed?
topic
end
2011-02-01 09:57:29 +08:00
end
end
2011-02-01 09:57:29 +08:00
def update_assignment
return if deleted?
allow using an item in modules more than once closes #8769 An item can be added to multiple modules, or even the same module more than once. This is especially useful for attachment items, but is also useful for allowing multiple paths through a course, with say an assignment in two different modules and the user only has to complete one of the two modules. test plan: For an item in only one module, verify that the module navigation still appears if you go straight to that item's page, without going through the modules page. Add an item to more than one module. If you visit that item from the modules page, you'll see the right nav depending on which instance of the item you clicked on. If you visit the item directly without going through the modules page, you'll see no nav. Lock one instance of the item by adding a prerequisite, but leave the other unlocked. You can still see the item as a student. Lock all instances of the item with prerequisites. The item will now be locked and you can't see it as a student. Add completion requirements to the item, such as a minimum score on a quiz. Make the requirements different -- 3 points in one instance and 5 in the other, for instance. Verify that if you get 3 points on the quiz, one item is marked as completed but the other isn't, as expected. Rename the item. Verify that all instances of it in modules get renamed. Change-Id: I4f1b2f6f033062ec47ac34fe5eb973a950c17b0c Reviewed-on: https://gerrit.instructure.com/11671 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Bracken Mosbacker <bracken@instructure.com>
2012-06-19 06:18:43 +08:00
if !assignment_id && @old_assignment_id
context_module_tags.find_each do |cmt|
cmt.confirm_valid_module_requirements
cmt.update_course_pace_module_items
end
2011-02-01 09:57:29 +08:00
end
if @old_assignment_id
Assignment.where(id: @old_assignment_id, context_id:, context_type:, submission_types: "discussion_topic").update_all(workflow_state: "deleted", updated_at: Time.now.utc)
old_assignment = Assignment.find(@old_assignment_id)
ContentTag.delete_for(old_assignment)
# prevent future syncs from recreating the deleted assignment
if is_child_content?
old_assignment.submission_types = "none"
own_tag = MasterCourses::ChildContentTag.where(content: self).take
own_tag&.child_subscription&.create_content_tag_for!(old_assignment, downstream_changes: ["workflow_state"])
end
elsif assignment && @saved_by != :assignment && !root_topic_id
Add WikiPage assignment type refs CYOE-67 Test Plan 0. `rake db:migrate` and compile assets. Enable the conditional release feature flag in course settings. 1. Create a published WikiPage, assignment-free. Ensure it is visible to errybody. 2. With the API, create an assignment with submission type 'wiki_page'. A new page will be created for the assignment. ```sh curl "http://canvas.dev:3000/api/v1/courses/1/assignments" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X POST -d '{"assignment": {"name": "Page Asg", "published":true, \ "submission_types":["wiki_page"]}}' ``` 3. Ensure the assignment is assigned to "Everyone" and the page is visible to all. 4. Assign the assignment to a specific student or section. Ensure the page is visible only to the assigned set. ```sh curl "http://canvas.dev:3000/api/v1/courses/1/assignments/13" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X PUT -d '{"assignment": {"only_visible_to_overrides": true }}' curl "http://canvas.dev:3000/api/v1/courses/1/assignments/13/overrides" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X POST -d '{"assignment_override": {"student_ids":[126]}}' ``` 5. Ensure the DA behavior also applies to WikiPage#show and update and edit. 6. Ensure API behavior matches docs for Assignment and Page endpoints. 7. Ensure no regressions in non-page assignments, with and without CR feature flag. Ensure no regressions in discussion topics and announcements, including assigned discussions. Change-Id: Ie53c6ffa4e4533cedd54f10aee3ba0b8a27aa4d5 Reviewed-on: https://gerrit.instructure.com/75139 Reviewed-by: Matt Berns <mberns@instructure.com> Tested-by: Jenkins QA-Review: Michael Hargiss <mhargiss@instructure.com> Product-Review: Christian Prescott <cprescott@instructure.com>
2016-04-08 00:37:16 +08:00
deleted_assignment = assignment.deleted?
sync_assignment
assignment.workflow_state = "published" if is_announcement && deleted_assignment
2011-02-01 09:57:29 +08:00
assignment.description = message
if saved_change_to_group_category_id?
DA - support student groups fixes CNVS-22360 Test Plan: - create a course with multiple course sections, multiple group sets (each with many groups), and students in these - create an assignment with overrides for groups - they work as expected - adhoc and section overrides continue to work as expected - ditto for graded discussions - check that discussion alerts go out properly - check that individually graded group discussions work as intended as well - note: child discussions will still be made for groups that dont have overrides (this is unfortunate but known) - note: quizzes not included in this PS - when editing an assignment try to assign a due date to a group not in the assigned group set - you cannot - when editing an assignment that is not a group assignment - confirm that you cannot assign a group override - if an assignment override for a group exists (and nobody else is assigned) - check that a student not in that group - does not have a spot in the GB - gets redirected away from that assignment if they go to the show page - does not have that assignment factored into their individual grade - does not have that assignment show up on their assignment index - if the student is then added to that group - all of the above conditions are reversed NOTE: if a student has a grade already, they will not 'lose visibility' until their grade is taken away (so removing a student from a group will reverse the above conditions only if they are gradeless) - check that the calendar accurately reflects the due dates for group overrides - with MGP on - test that group overrides act in the same way as course_section overrides in regards to grading period Change-Id: Ia32f57feabbc719568901e0763130b3c2304c7ce Reviewed-on: https://gerrit.instructure.com/69060 Tested-by: Jenkins Reviewed-by: Simon Williams <simon@instructure.com> QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com> QA-Review: Michael Hargiss <mhargiss@instructure.com> Product-Review: Jason Sparks <jsparks@instructure.com>
2015-12-17 00:35:49 +08:00
assignment.validate_assignment_overrides(force_override_destroy: true)
end
2011-02-01 09:57:29 +08:00
assignment.save
end
# make sure that if the topic has a new assignment (either by going from
# ungraded to graded, or from one assignment to another; we ignore the
# transition from graded to ungraded) we acknowledge that the users that
# have posted have contributed to the topic and that course paces are up
# to date
if assignment_id && saved_change_to_assignment_id?
recalculate_context_module_actions!
context_module_tags.find_each(&:update_course_pace_module_items)
end
2011-02-01 09:57:29 +08:00
end
protected :update_assignment
def recalculate_context_module_actions!
posters.each { |user| context_module_action(user, :contributed) }
end
2011-02-01 09:57:29 +08:00
def is_announcement
false
end
def homeroom_announcement?(_context)
false
end
2011-02-01 09:57:29 +08:00
def root_topic?
!root_topic_id && has_group_category?
2011-02-01 09:57:29 +08:00
end
# only the root level entries
2011-02-01 09:57:29 +08:00
def discussion_subentries
root_discussion_entries
end
# count of all active discussion_entries
2011-02-01 09:57:29 +08:00
def discussion_subentry_count
discussion_entries.active.count
2011-02-01 09:57:29 +08:00
end
def for_group_discussion?
has_group_category? && root_topic?
2011-02-01 09:57:29 +08:00
end
2011-02-01 09:57:29 +08:00
def plaintext_message=(val)
self.message = format_message(strip_tags(val)).first
end
2011-02-01 09:57:29 +08:00
def plaintext_message
truncate_html(message, max_length: 250)
end
def create_participant
discussion_topic_participants.create(user:, workflow_state: "read", unread_entry_count: 0, subscribed: !subscription_hold(user, nil)) if user
end
def update_materialized_view
# kick off building of the view
self.class.connection.after_transaction_commit do
DiscussionTopic::MaterializedView.for(self).update_materialized_view(xlog_location: self.class.current_xlog_location)
end
end
def group_category_deleted_with_entries?
group_category.try(:deleted_at?) && !can_group?
end
def get_potentially_conflicting_titles(title_base)
DiscussionTopic.active.where(context_type:, context_id:)
.starting_with_title(title_base).pluck("title").to_set
end
# This is a guess of what to copy over.
def duplicate_base_model(title, opts)
DiscussionTopic.new({
title:,
message:,
context_id:,
context_type:,
user_id: opts[:user] ? opts[:user].id : user_id,
type:,
workflow_state: "unpublished",
could_be_locked:,
context_code:,
podcast_enabled:,
require_initial_post:,
podcast_has_student_posts:,
discussion_type:,
delayed_post_at:,
lock_at:,
pinned:,
locked:,
group_category_id:,
allow_rating:,
only_graders_can_rate:,
sort_by_rating:,
todo_date:,
is_section_specific:,
anonymous_state:
})
end
# Presumes that self has no parents
# Does not duplicate the child topics; the hooks take care of that for us.
def duplicate(opts = {})
# Don't clone a new record
return self if new_record?
default_opts = {
duplicate_assignment: true,
copy_title: nil,
user: nil
}
opts_with_default = default_opts.merge(opts)
copy_title =
opts_with_default[:copy_title] || get_copy_title(self, t("Copy"), title)
result = duplicate_base_model(copy_title, opts_with_default)
Fix position handling for pinned discussions. This makes sure duplication handles positions for pinned discussions, and we render discussions moved to the pinned section as the last one in that section. This should result in the ordering of pinned discussions not changing just because we hit reload, and keeps the frontend and backend on the same page on the positioning of pinned discussions. Also this refactors the SSD selenium specs since they were being flaky. The only thing I can think of is having 4 discussions on a course page is too many, so I refactored things to create new courses and discussions if I needed a different course than was there already. Fixes COMMS-954 Test Plan: * Have section specific discussions enabled. * Have some pinned discussions. * Duplicate some. Note the order of the discussions. * Reload. The order should be preserved. * Make one of these pinned discussions *graded* * Go to the assignments page, and duplicate the corresponding assignment. * Go back to the discussions page, the new graded discussion should be right below the original. * Mix and match this with some other operations, it should all still work, and stuff in the pinned section shouldn't mysteriously move around just because of a reload. Change-Id: I5f699689290d5c70158940865e2193ea8f7d403a Reviewed-on: https://gerrit.instructure.com/143365 Tested-by: Jenkins Reviewed-by: Steven Burnett <sburnett@instructure.com> Reviewed-by: Landon Gilbert-Bland <lbland@instructure.com> QA-Review: Aaron Kc Hsu <ahsu@instructure.com> Product-Review: Steven Burnett <sburnett@instructure.com>
2018-03-13 05:08:20 +08:00
# Start with a position guaranteed to not conflict with existing ones.
# Clients are encouraged to set the correct position later on and do
# an insert_at upon save.
if pinned
result.position = context.discussion_topics.active.where(pinned: true).maximum(:position) + 1
end
if assignment && opts_with_default[:duplicate_assignment]
result.assignment = assignment.duplicate({
duplicate_discussion_topic: false,
copy_title: result.title
})
end
result.discussion_topic_section_visibilities = []
if is_section_specific
original_visibilities = discussion_topic_section_visibilities.active
original_visibilities.each do |visibility|
new_visibility = DiscussionTopicSectionVisibility.new(
discussion_topic: result,
course_section: visibility.course_section
)
result.discussion_topic_section_visibilities << new_visibility
end
end
# For some reason, the relation doesn't take care of this for us. Don't understand why.
# Without this line, *two* discussion topic duplicates appear when a save is performed.
result.assignment&.discussion_topic = result
result
end
# If no join record exists, assume all discussion enrties are unread, and
# that a join record will be created the first time one is marked as read.
attr_accessor :current_user
def read_state(current_user = nil)
current_user ||= self.current_user
return "read" unless current_user # default for logged out user
uid = current_user.is_a?(User) ? current_user.id : current_user
ws = if discussion_topic_participants.loaded?
discussion_topic_participants.detect { |dtp| dtp.user_id == uid }&.workflow_state
else
discussion_topic_participants.where(user_id: uid).pick(:workflow_state)
end
ws || "unread"
end
def read?(current_user = nil)
read_state(current_user) == "read"
end
def unread?(current_user = nil)
!read?(current_user)
end
def change_read_state(new_state, current_user = nil)
current_user ||= self.current_user
return nil unless current_user
context_module_action(current_user, :read) if new_state == "read"
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
return true if new_state == read_state(current_user)
new dashboard design the new dashboard design categorizes recent activity into buckets that can be expanded/collapsed, and inidividual messages can be dismissed. the categories are announcements, conversations, discussions and assignments. this redesign applies to the homepage dashboard, the group home page, and the course homepage when "recent activity dashboard" is selected as the course home page type.o the motiviation is that the dashboard should capture and present in one place important information happening in all the user's courses or groups, and allow for jumping into this information to see more details: - announcements/discussions should show on the dashboard when they are created, or when there are root replies to recent announcements - conversations should show on the dashboard when there is new activity - assignments should show on the dashboard when they are created, or when changes are made at least a couple hours after being created the presence of a dashboard item means there is activity for that item that may be of interest to the user. additionally, the dashboard items will show read/unread state (excluding assignments) for items which the user has not yet viewed. additionally, global messages such as course inivitations, account level announcements, and new user messages have been restyled, but will keep their place above the recent activity widget on the dashboard. test plan: - visit many exising user's dashboards and make sure they are functional in the new style. - visit canvas as a brand new user (no enrollments), a new user enrolled in a new course and make sure the dashboard is restyled and the messaging makes sense. - make an account level announcement and make sure it shows up on user's dashboards. - create all different types of conversations: single, group, bulk private, from submission comment, add user to convo, etc. and make sure the appropriate dashboard items appear and make sense - create discussions and announcements, reply to them at the root level and at the sub entry level (sub entries will not make new dashboard items), test from both a read and unread user's perspective, making sure dashboard items are correct. (note that read/unread state will not be correct for existing items before this code is applied, but should be correct for future items moving forward) - dismiss dashboard items and account announcements, make sure they stay dismissed. - test creating assignments, waiting > 2 hours, and updating due dates or other assignment details. make sure items appear. note that unread state will not exist for assignment notifications. closes #10783 refs #11038 refs #11039 Change-Id: I276a8cb1fae4c8a46425d0a368455e15a0c470c5 Reviewed-on: https://gerrit.instructure.com/14540 Reviewed-by: Jon Jensen <jon@instructure.com> Tested-by: Jenkins <jenkins@instructure.com>
2012-10-05 05:49:54 +08:00
StreamItem.update_read_state_for_asset(self, new_state, current_user.id)
update_or_create_participant(current_user:, new_state:)
end
def change_all_read_state(new_state, current_user = nil, opts = {})
current_user ||= self.current_user
return unless current_user
update_fields = { workflow_state: new_state }
update_fields[:forced_read_state] = opts[:forced] if opts.key?(:forced)
transaction do
update_stream_item_state(current_user, new_state)
update_participants_read_state(current_user, new_state, update_fields)
end
end
def update_stream_item_state(current_user, new_state)
context_module_action(current_user, :read) if new_state == "read"
StreamItem.update_read_state_for_asset(self, new_state, current_user.id)
end
protected :update_stream_item_state
def update_participants_read_state(current_user, new_state, update_fields)
# if workflow_state is unread, and force_read_state is not provided then
# mark everything as unread but use the defaults, or allow other entries to
# be implicitly unread, but still update any existing records.
if new_state == "unread" && !update_fields.key?(:forced_read_state)
DiscussionEntryParticipant.where(discussion_entry_id: discussion_entries.select(:id), user: current_user)
.where.not(workflow_state: new_state)
.in_batches.update_all(update_fields)
else
DiscussionEntryParticipant.upsert_for_topic(self,
current_user,
new_state:,
forced: update_fields[:forced_read_state])
end
update_or_create_participant(current_user:,
new_state:,
new_count: (new_state == "unread") ? default_unread_count : 0)
end
protected :update_participants_read_state
def default_unread_count
discussion_entries.active.count
end
# Do not use the lock options unless you truly need
# the lock, for instance to update the count.
# Careless use has caused database transaction deadlocks
def unread_count(current_user = nil, lock: false, opts: {})
current_user ||= self.current_user
return 0 unless current_user # default for logged out users
environment = lock ? :primary : :secondary
GuardRail.activate(environment) do
topic_participant = if opts[:use_preload] && association(:discussion_topic_participants).loaded?
discussion_topic_participants.find { |dtp| dtp.user_id == current_user.id }
else
discussion_topic_participants.where(user_id: current_user).select(:unread_entry_count).lock(lock).take
end
topic_participant&.unread_entry_count || default_unread_count
end
end
# Cases where you CAN'T subscribe:
# - initial post is required and you haven't made one
# - it's an announcement
# - this is a root level graded group discussion and you aren't in any of the groups
# - this is group level discussion and you aren't in the group
def subscription_hold(user, session)
return nil unless user
if initial_post_required?(user, session)
:initial_post_required
elsif root_topic? && !child_topic_for(user)
:not_in_group_set
elsif context.is_a?(Group) && !context.has_member?(user)
:not_in_group
end
end
def subscribed?(current_user = nil, opts: {})
current_user ||= self.current_user
return false unless current_user # default for logged out user
if root_topic?
participant = DiscussionTopicParticipant.where(user_id: current_user.id,
discussion_topic_id: child_topics.pluck(:id)).take
end
participant ||= if opts[:use_preload] && association(:discussion_topic_participants).loaded?
discussion_topic_participants.find { |dtp| dtp.user_id == current_user.id }
else
discussion_topic_participants.where(user_id: current_user).take
end
if participant
if participant.subscribed.nil?
# if there is no explicit subscription, assume the author and posters
# are subscribed, everyone else is not subscribed
(current_user == user || participant.discussion_topic.posters.include?(current_user)) && !participant.discussion_topic.subscription_hold(current_user, nil)
else
participant.subscribed
end
else
current_user == user && !subscription_hold(current_user, nil)
end
end
def subscribe(current_user = nil)
change_subscribed_state(true, current_user)
end
def unsubscribe(current_user = nil)
change_subscribed_state(false, current_user)
end
def change_subscribed_state(new_state, current_user = nil)
current_user ||= self.current_user
return unless current_user
return true if subscribed?(current_user) == new_state
if root_topic?
return if change_child_topic_subscribed_state(new_state, current_user)
ctss = DiscussionTopicParticipant.new
ctss.errors.add(:discussion_topic_id, I18n.t("no child topic found"))
ctss
else
update_or_create_participant(current_user:, subscribed: new_state)
end
end
def child_topic_for(user)
return unless context.is_a?(Course)
group_ids = user.group_memberships.active.pluck(:group_id) &
context.groups.active.pluck(:id)
child_topics.active.where(context_id: group_ids, context_type: "Group").first
end
def change_child_topic_subscribed_state(new_state, current_user)
topic = child_topic_for(current_user)
topic&.update_or_create_participant(current_user:, subscribed: new_state)
end
protected :change_child_topic_subscribed_state
def update_or_create_participant(opts = {})
current_user = opts[:current_user] || self.current_user
return nil unless current_user
topic_participant = nil
GuardRail.activate(:primary) do
DiscussionTopic.uncached do
DiscussionTopic.unique_constraint_retry do
topic_participant = discussion_topic_participants.where(user_id: current_user).lock.first
topic_participant ||= discussion_topic_participants.build(user: current_user,
unread_entry_count: unread_count(current_user, lock: true),
workflow_state: "unread",
subscribed: current_user == user && !subscription_hold(current_user, nil))
topic_participant.workflow_state = opts[:new_state] if opts[:new_state]
topic_participant.unread_entry_count += opts[:offset] if opts[:offset] && opts[:offset] != 0
topic_participant.unread_entry_count = opts[:new_count] if opts[:new_count]
topic_participant.subscribed = opts[:subscribed] if opts.key?(:subscribed)
topic_participant.save
end
end
end
topic_participant
end
scope :not_ignored_by, lambda { |user, purpose|
where.not(Ignore.where(asset_type: "DiscussionTopic", user_id: user, purpose:)
.where("asset_id=discussion_topics.id").arel.exists)
}
scope :todo_date_between, lambda { |starting, ending|
where("(discussion_topics.type = 'Announcement' AND posted_at BETWEEN :start_at and :end_at)
OR todo_date BETWEEN :start_at and :end_at",
{ start_at: starting, end_at: ending })
}
scope :for_courses_and_groups, lambda { |course_ids, group_ids|
where("(discussion_topics.context_type = 'Course'
AND discussion_topics.context_id IN (?))
OR (discussion_topics.context_type = 'Group'
AND discussion_topics.context_id IN (?))",
course_ids,
group_ids)
}
class QueryError < StandardError
attr_accessor :status_code
def initialize(message = nil, status_code = nil)
super(message)
self.status_code = status_code
end
end
# Retrieves all the *course* (as oppposed to group) discussion topics that apply
# to the given sections. Group topics will not be returned. TODO: figure out
# a good way to deal with group topics here.
#
# Takes in an array of section objects, and it is required that they all belong
# to the same course. At least one section must be provided.
scope :in_sections, lambda { |course_sections|
course_ids = course_sections.pluck(:course_id).uniq
if course_ids.length != 1
raise QueryError, I18n.t("Searching for announcements in sections must span exactly one course")
end
course_id = course_ids.first
joins("LEFT OUTER JOIN #{DiscussionTopicSectionVisibility.quoted_table_name}
AS discussion_section_visibilities ON discussion_topics.is_section_specific = true AND
discussion_section_visibilities.discussion_topic_id = discussion_topics.id")
.where("discussion_topics.context_type = 'Course' AND
discussion_topics.context_id = :course_id",
{ course_id: })
.where("discussion_section_visibilities.id IS null OR
(discussion_section_visibilities.workflow_state = 'active' AND
discussion_section_visibilities.course_section_id IN (:course_sections))",
{ course_sections: course_sections.pluck(:id) }).distinct
}
scope :visible_to_student_sections, lambda { |student|
visibility_scope = DiscussionTopicSectionVisibility
.active
.where("discussion_topic_section_visibilities.discussion_topic_id = discussion_topics.id")
.where(
Enrollment.active_or_pending.where(user_id: student)
.where("enrollments.course_section_id = discussion_topic_section_visibilities.course_section_id")
.arel.exists
)
merge(
DiscussionTopic.where.not(discussion_topics: { context_type: "Course" })
.or(DiscussionTopic.where(discussion_topics: { is_section_specific: false }))
.or(DiscussionTopic.where(visibility_scope.arel.exists))
)
}
scope :recent, -> { where("discussion_topics.last_reply_at>?", 2.weeks.ago).order("discussion_topics.last_reply_at DESC") }
scope :only_discussion_topics, -> { where(type: nil) }
scope :for_subtopic_refreshing, -> { where("discussion_topics.subtopics_refreshed_at IS NOT NULL AND discussion_topics.subtopics_refreshed_at<discussion_topics.updated_at").order("discussion_topics.subtopics_refreshed_at") }
scope :active, -> { where("discussion_topics.workflow_state<>'deleted'") }
scope :for_context_codes, ->(codes) { where(context_code: codes) }
scope :before, ->(date) { where("discussion_topics.created_at<?", date) }
scope :by_position, -> { order("discussion_topics.position ASC, discussion_topics.created_at DESC, discussion_topics.id DESC") }
scope :by_position_legacy, -> { order("discussion_topics.position DESC, discussion_topics.created_at DESC, discussion_topics.id DESC") }
scope :by_last_reply_at, -> { order("discussion_topics.last_reply_at DESC, discussion_topics.created_at DESC, discussion_topics.id DESC") }
scope :by_posted_at, lambda {
order(Arel.sql(<<~SQL.squish))
fix blueprint sync for discussions locked in the past test plan: - have a blueprint/associated course set up - create an ungraded discussion topic in the blueprint and set its "available until" date in the past - perform a sync - change the "available until" date in the blueprint to be in the future - perform another sync - the discussion in the associated course should be updated (no sync exception) extra credit: - do the above, but with an available-until date in the near future. perform the sync before the discussion locks. wait until after it locks, and note that the locking has happened in the associated course. do not manually edit the discussion in the associated course. change the date in the blueprint to be in the future, perform a sync, and the discussion in the associated course should be unlocked again bonus: - manually edit the discussion in the associated course and change the availability dates - also change the blueprint dates and perform a sync - the changes in the associated course should _not_ be overwritten (sync exception) - lock availability dates in the blueprint discussion and make another edit to it - perform a sync and the changes _should_ be overwritten flag = none fixes LS-1641 Change-Id: Id105f6d7e526c11f984fb786e66491b4862e3cfb Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/254794 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Ed Schiebel <eschiebel@instructure.com> Reviewed-by: Mysti Lilla <mysti@instructure.com> QA-Review: Nate Armstrong <narmstrong@instructure.com> Product-Review: Jeremy Stanley <jeremy@instructure.com>
2020-12-10 12:40:39 +08:00
COALESCE(discussion_topics.delayed_post_at, discussion_topics.posted_at, discussion_topics.created_at) DESC,
discussion_topics.created_at DESC,
discussion_topics.id DESC
SQL
}
BookmarkedCollection for Planner API Closes FALCOR-325 Fixes FALCOR-361 Test Plan Date Range Testing: * With "Student Planner" enabled * As a student * Make a GET request to /api/v1/planner/items with ?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD&filter=new_activity * The response should only contain results with new activity between those time ranges (test with dates and dates with times) Pagination Testing: * With "Student Planner" enabled * As a student * Make a GET request to /api/v1/planner/items with some combination of start_date, end_date, and filter * Include a a `per_page` param * The response should only contain only `per_page` number of results * The response should contain a `Link` header with links to the current, next, and previous pages (you should see a URL with a `page=bookmark:[bunch of characters]`) * Copy the `page` param from the `Link` header for the next page of results * Make a request with the newly copied `page=bookmark:[stuff]` parameter added to the request * You should get the next page of results in the response * The previous and next URLs in the `Link` response header should return the previous and next page of results Regression Testing: * Full regression test of "New Student Planner" Change-Id: Ie6352aec71f5de3c60981aaeee65119cc43ed6f7 Reviewed-on: https://gerrit.instructure.com/114848 Reviewed-by: Steven Burnett <sburnett@instructure.com> Tested-by: Jenkins QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com> Product-Review: Dan Minkevitch <dan@instructure.com>
2017-06-09 10:52:02 +08:00
scope :read_for, lambda { |user|
eager_load(:discussion_topic_participants)
.where("discussion_topic_participants.id IS NOT NULL
AND (discussion_topic_participants.user_id = :user
AND discussion_topic_participants.workflow_state = 'read')",
user:)
BookmarkedCollection for Planner API Closes FALCOR-325 Fixes FALCOR-361 Test Plan Date Range Testing: * With "Student Planner" enabled * As a student * Make a GET request to /api/v1/planner/items with ?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD&filter=new_activity * The response should only contain results with new activity between those time ranges (test with dates and dates with times) Pagination Testing: * With "Student Planner" enabled * As a student * Make a GET request to /api/v1/planner/items with some combination of start_date, end_date, and filter * Include a a `per_page` param * The response should only contain only `per_page` number of results * The response should contain a `Link` header with links to the current, next, and previous pages (you should see a URL with a `page=bookmark:[bunch of characters]`) * Copy the `page` param from the `Link` header for the next page of results * Make a request with the newly copied `page=bookmark:[stuff]` parameter added to the request * You should get the next page of results in the response * The previous and next URLs in the `Link` response header should return the previous and next page of results Regression Testing: * Full regression test of "New Student Planner" Change-Id: Ie6352aec71f5de3c60981aaeee65119cc43ed6f7 Reviewed-on: https://gerrit.instructure.com/114848 Reviewed-by: Steven Burnett <sburnett@instructure.com> Tested-by: Jenkins QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com> Product-Review: Dan Minkevitch <dan@instructure.com>
2017-06-09 10:52:02 +08:00
}
scope :unread_for, lambda { |user|
joins(sanitize_sql(["LEFT OUTER JOIN #{DiscussionTopicParticipant.quoted_table_name} ON
discussion_topic_participants.discussion_topic_id=discussion_topics.id AND
discussion_topic_participants.user_id=?",
user.id]))
.where("discussion_topic_participants IS NULL
OR discussion_topic_participants.workflow_state <> 'read'
OR discussion_topic_participants.unread_entry_count > 0")
BookmarkedCollection for Planner API Closes FALCOR-325 Fixes FALCOR-361 Test Plan Date Range Testing: * With "Student Planner" enabled * As a student * Make a GET request to /api/v1/planner/items with ?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD&filter=new_activity * The response should only contain results with new activity between those time ranges (test with dates and dates with times) Pagination Testing: * With "Student Planner" enabled * As a student * Make a GET request to /api/v1/planner/items with some combination of start_date, end_date, and filter * Include a a `per_page` param * The response should only contain only `per_page` number of results * The response should contain a `Link` header with links to the current, next, and previous pages (you should see a URL with a `page=bookmark:[bunch of characters]`) * Copy the `page` param from the `Link` header for the next page of results * Make a request with the newly copied `page=bookmark:[stuff]` parameter added to the request * You should get the next page of results in the response * The previous and next URLs in the `Link` response header should return the previous and next page of results Regression Testing: * Full regression test of "New Student Planner" Change-Id: Ie6352aec71f5de3c60981aaeee65119cc43ed6f7 Reviewed-on: https://gerrit.instructure.com/114848 Reviewed-by: Steven Burnett <sburnett@instructure.com> Tested-by: Jenkins QA-Review: Deepeeca Soundarrajan <dsoundarrajan@instructure.com> Product-Review: Dan Minkevitch <dan@instructure.com>
2017-06-09 10:52:02 +08:00
}
scope :published, -> { where("discussion_topics.workflow_state = 'active'") }
# TODO: this scope is appearing in a few models now with identical code.
# Can this be extracted somewhere?
scope :starting_with_title, lambda { |title|
where("title ILIKE ?", "#{title}%")
}
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
alias_attribute :available_from, :delayed_post_at
alias_attribute :unlock_at, :delayed_post_at
alias_attribute :available_until, :lock_at
def should_lock_yet
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
# not assignment or vdd aware! only use this to check the topic's own field!
# you should be checking other lock statuses in addition to this one
lock_at && lock_at < Time.now.utc
added auto lock discussions on specified date fixes CNVS-4106 test steps: - go to create a discussion topic and ensure that you can toggle between the 'Graded' and non-graded availability dates. If the Graded is not checked, you should see the Available From/Until. - create a discussion topic with a past from and past until. ensure that the discussion is locked for a student - create a discussion topic with a future from and future until. ensure the discussion cannot be seen by the student - update the above created discussion topic and switch it to be graded and set a future due, from, until date. ensure that the student cannot see the discussion. - edit the discussion and ensure that the graded checkbox is checked and the group assignment fields are showing - update the assignment with a past due, from, until and make sure the discussion is locked for the student. - do all above with current dates (from in past, until in future) and make sure the student can see and reply to them. - create an announcement and ensure that the functionality and forms are the same as before - verify that you are able to lock and unlock discussion topics on the show view by using the gear drop-down Change-Id: I99c9d54763fe3a74aa8a4bb37c22f09d4765d41e Reviewed-on: https://gerrit.instructure.com/20339 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Reviewed-by: Zach Pendleton <zachp@instructure.com> QA-Review: Marc LeGendre <marc@instructure.com> Product-Review: Marc LeGendre <marc@instructure.com>
2013-05-03 07:39:26 +08:00
end
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
alias_method :not_available_anymore?, :should_lock_yet
added auto lock discussions on specified date fixes CNVS-4106 test steps: - go to create a discussion topic and ensure that you can toggle between the 'Graded' and non-graded availability dates. If the Graded is not checked, you should see the Available From/Until. - create a discussion topic with a past from and past until. ensure that the discussion is locked for a student - create a discussion topic with a future from and future until. ensure the discussion cannot be seen by the student - update the above created discussion topic and switch it to be graded and set a future due, from, until date. ensure that the student cannot see the discussion. - edit the discussion and ensure that the graded checkbox is checked and the group assignment fields are showing - update the assignment with a past due, from, until and make sure the discussion is locked for the student. - do all above with current dates (from in past, until in future) and make sure the student can see and reply to them. - create an announcement and ensure that the functionality and forms are the same as before - verify that you are able to lock and unlock discussion topics on the show view by using the gear drop-down Change-Id: I99c9d54763fe3a74aa8a4bb37c22f09d4765d41e Reviewed-on: https://gerrit.instructure.com/20339 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Reviewed-by: Zach Pendleton <zachp@instructure.com> QA-Review: Marc LeGendre <marc@instructure.com> Product-Review: Marc LeGendre <marc@instructure.com>
2013-05-03 07:39:26 +08:00
def should_not_post_yet
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
# not assignment or vdd aware! only use this to check the topic's own field!
# you should be checking other lock statuses in addition to this one
delayed_post_at && delayed_post_at > Time.now.utc
added auto lock discussions on specified date fixes CNVS-4106 test steps: - go to create a discussion topic and ensure that you can toggle between the 'Graded' and non-graded availability dates. If the Graded is not checked, you should see the Available From/Until. - create a discussion topic with a past from and past until. ensure that the discussion is locked for a student - create a discussion topic with a future from and future until. ensure the discussion cannot be seen by the student - update the above created discussion topic and switch it to be graded and set a future due, from, until date. ensure that the student cannot see the discussion. - edit the discussion and ensure that the graded checkbox is checked and the group assignment fields are showing - update the assignment with a past due, from, until and make sure the discussion is locked for the student. - do all above with current dates (from in past, until in future) and make sure the student can see and reply to them. - create an announcement and ensure that the functionality and forms are the same as before - verify that you are able to lock and unlock discussion topics on the show view by using the gear drop-down Change-Id: I99c9d54763fe3a74aa8a4bb37c22f09d4765d41e Reviewed-on: https://gerrit.instructure.com/20339 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Reviewed-by: Zach Pendleton <zachp@instructure.com> QA-Review: Marc LeGendre <marc@instructure.com> Product-Review: Marc LeGendre <marc@instructure.com>
2013-05-03 07:39:26 +08:00
end
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
alias_method :not_available_yet?, :should_not_post_yet
added auto lock discussions on specified date fixes CNVS-4106 test steps: - go to create a discussion topic and ensure that you can toggle between the 'Graded' and non-graded availability dates. If the Graded is not checked, you should see the Available From/Until. - create a discussion topic with a past from and past until. ensure that the discussion is locked for a student - create a discussion topic with a future from and future until. ensure the discussion cannot be seen by the student - update the above created discussion topic and switch it to be graded and set a future due, from, until date. ensure that the student cannot see the discussion. - edit the discussion and ensure that the graded checkbox is checked and the group assignment fields are showing - update the assignment with a past due, from, until and make sure the discussion is locked for the student. - do all above with current dates (from in past, until in future) and make sure the student can see and reply to them. - create an announcement and ensure that the functionality and forms are the same as before - verify that you are able to lock and unlock discussion topics on the show view by using the gear drop-down Change-Id: I99c9d54763fe3a74aa8a4bb37c22f09d4765d41e Reviewed-on: https://gerrit.instructure.com/20339 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Reviewed-by: Zach Pendleton <zachp@instructure.com> QA-Review: Marc LeGendre <marc@instructure.com> Product-Review: Marc LeGendre <marc@instructure.com>
2013-05-03 07:39:26 +08:00
# There may be delayed jobs that expect to call this to update the topic, so be sure to alias
# the old method name if you change it
fix blueprint sync for discussions locked in the past test plan: - have a blueprint/associated course set up - create an ungraded discussion topic in the blueprint and set its "available until" date in the past - perform a sync - change the "available until" date in the blueprint to be in the future - perform another sync - the discussion in the associated course should be updated (no sync exception) extra credit: - do the above, but with an available-until date in the near future. perform the sync before the discussion locks. wait until after it locks, and note that the locking has happened in the associated course. do not manually edit the discussion in the associated course. change the date in the blueprint to be in the future, perform a sync, and the discussion in the associated course should be unlocked again bonus: - manually edit the discussion in the associated course and change the availability dates - also change the blueprint dates and perform a sync - the changes in the associated course should _not_ be overwritten (sync exception) - lock availability dates in the blueprint discussion and make another edit to it - perform a sync and the changes _should_ be overwritten flag = none fixes LS-1641 Change-Id: Id105f6d7e526c11f984fb786e66491b4862e3cfb Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/254794 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Ed Schiebel <eschiebel@instructure.com> Reviewed-by: Mysti Lilla <mysti@instructure.com> QA-Review: Nate Armstrong <narmstrong@instructure.com> Product-Review: Jeremy Stanley <jeremy@instructure.com>
2020-12-10 12:40:39 +08:00
# Also: if this method is scheduled by a blueprint sync, ensure it isn't counted as a manual downstream change
def update_based_on_date(for_blueprint: false)
skip_downstream_changes! if for_blueprint
transaction do
reload lock: true # would call lock!, except, oops, workflow overwrote it :P
lock if should_lock_yet
delayed_post unless should_not_post_yet
end
2011-02-01 09:57:29 +08:00
end
alias_method :try_posting_delayed, :update_based_on_date
alias_method :auto_update_workflow, :update_based_on_date
2011-02-01 09:57:29 +08:00
workflow do
state :active
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
state :unpublished
2011-02-01 09:57:29 +08:00
state :post_delayed do
event :delayed_post, transitions_to: :active do
self.last_reply_at = Time.now
self.posted_at = Time.now
end
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
# with draft state, this means published. without, unpublished. so we really do support both events
2011-02-01 09:57:29 +08:00
end
state :deleted
end
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
def active?
# using state instead of workflow_state so this works with new records
state == :active || (!is_announcement && state == :post_delayed)
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
end
def publish
respect delayed_post_at when publishing flag=none fixes VICE-3586 explanation: this issue has more to do with publishing an existing unpublished discussion than with duplicating discussions the reason why the original discussion did not show in calendar and dashboard list view is because its workflow_state is post_delayed (even when you save and publish on creation) the reason why the duplicated discussion begins to show in student calendar and dashboard is because when you publish an unpublished discussion (in this case, the duplicated discussion), its workflow state is set to active without even checking for the delayed_post_at attribute of the discussion this commit makes it so that when you publish an unpublished discussion, we set the worklow_state to post_delayed instead of active if delayed_post_at is in the future. test plan: - create a discussion topic - in the create form, set the todo date to 2 days from now - set the available from to 1 day from now - set the until date to 3 days from now - save (do not publish) - in the discussions index, publish - as a student, visit the list view of your dashboard - verify you do not see the discussion - visit the calendar - wait for the loading spinner on the top right to go away - verify you do not see the discussion - now as a teacher, go back to discussions index and duplicate the discussion - publish the duplicated discussion - as a student, check your dashboard list view and calendar again - verify you do not see the duplicated discussion - bundle exec rails c - DiscussionTopic.last should be your duplicated topic - DiscussionTopic.last.delayed_post_at = 2.minutes.from_now - DiscussionTopic.last.save! - DiscussionTopic.last.reload - as a student wait for 2 minutes to pass - then revisit the calendar and the dashboard list view and verify you now see the duplicated discussion Change-Id: I786a495f6e03aa427c0958f5f6a43bb1c5eea1d3 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/324436 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Omar Soto-Fortuño <omar.soto@instructure.com> Product-Review: Omar Soto-Fortuño <omar.soto@instructure.com> QA-Review: Chawn Neal <chawn.neal@instructure.com>
2023-08-04 07:49:26 +08:00
# follows the logic of setting post_delayed in other places of this file
self.workflow_state = (delayed_post_at && delayed_post_at > Time.now) ? "post_delayed" : "active"
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
self.last_reply_at = Time.now
self.posted_at = Time.now
end
def publish!
publish
save!
end
def unpublish
self.workflow_state = "unpublished"
end
def unpublish!
unpublish
save!
end
def can_lock?
!(assignment.try(:due_at) && assignment.due_at > Time.now)
end
def comments_disabled?
!!(is_a?(Announcement) &&
context.is_a?(Course) &&
context.lock_all_announcements)
end
2013-07-09 06:58:54 +08:00
def lock(opts = {})
raise Errors::LockBeforeDueDate unless can_lock?
self.locked = true
2013-07-09 06:58:54 +08:00
save! unless opts[:without_save]
end
alias_method :lock!, :lock
2013-07-09 06:58:54 +08:00
def unlock(opts = {})
self.locked = false
self.workflow_state = "active" if workflow_state == "locked"
2013-07-09 06:58:54 +08:00
save! unless opts[:without_save]
end
alias_method :unlock!, :unlock
2013-07-09 06:58:54 +08:00
def published?
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
return false if workflow_state == "unpublished"
return false if workflow_state == "post_delayed" && is_announcement
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
true
2013-07-09 06:58:54 +08:00
end
def can_unpublish?(opts = {})
return @can_unpublish unless @can_unpublish.nil?
@can_unpublish = if assignment
!assignment.has_student_submissions?
else
student_ids = opts[:student_ids] || context.all_real_student_enrollments.select(:user_id)
if for_group_discussion?
!DiscussionEntry.active.joins(:discussion_topic).merge(child_topics).where(user_id: student_ids).exists?
else
!discussion_entries.active.where(user_id: student_ids).exists?
end
end
end
attr_writer :can_unpublish
def self.preload_can_unpublish(context, topics, assmnt_ids_with_subs = nil)
return unless topics.any?
assmnt_ids_with_subs ||= Assignment.assignment_ids_with_submissions(topics.filter_map(&:assignment_id))
student_ids = context.all_real_student_enrollments.select(:user_id)
topic_ids_with_entries = DiscussionEntry.active.where(discussion_topic_id: topics)
.where(user_id: student_ids).distinct.pluck(:discussion_topic_id)
topic_ids_with_entries += DiscussionTopic.where.not(root_topic_id: nil)
.where(id: topic_ids_with_entries).distinct.pluck(:root_topic_id)
topics.each do |topic|
topic.can_unpublish = if topic.assignment_id
!assmnt_ids_with_subs.include?(topic.assignment_id)
else
!topic_ids_with_entries.include?(topic.id)
end
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
end
end
def can_group?(opts = {})
can_unpublish?(opts)
end
def should_send_to_stream
published? &&
!not_available_yet? &&
!cloned_item_id &&
!(root_topic_id && has_group_category?) &&
!in_unpublished_module? &&
!locked_by_module?
end
on_create_send_to_streams do
if should_send_to_stream
active_participants_with_visibility
2011-02-01 09:57:29 +08:00
end
end
2011-02-01 09:57:29 +08:00
on_update_send_to_streams do
check_state = is_announcement ? "post_delayed" : "unpublished"
became_active = workflow_state_before_last_save == check_state && workflow_state == "active"
if should_send_to_stream && (@content_changed || became_active)
active_participants_with_visibility
2011-02-01 09:57:29 +08:00
end
end
# This is manually called for module publishing
def send_items_to_stream
if should_send_to_stream
queue_create_stream_items
end
end
def in_unpublished_module?
return true if ContentTag.where(content_type: "DiscussionTopic", content_id: self, workflow_state: "unpublished").exists?
ContextModule.joins(:content_tags).where(content_tags: { content_type: "DiscussionTopic", content_id: self }, workflow_state: "unpublished").exists?
end
def locked_by_module?
return false unless context_module_tags.any?
ContentTag.where(content_type: "DiscussionTopic", content_id: self, workflow_state: "active").all? { |tag| tag.context_module.unlock_at&.future? }
end
def should_clear_all_stream_items?
(!published? && saved_change_to_attribute?(:workflow_state)) ||
(is_announcement && not_available_yet? && saved_change_to_attribute?(:delayed_post_at))
end
def clear_non_applicable_stream_items
return clear_stream_items if should_clear_all_stream_items?
section = is_section_specific? ? @sections_changed : is_section_specific_before_last_save
lock = locked_by_module?
if lock || section
delay_if_production.partially_clear_stream_items(locked_by_module: lock, section_specific: section)
end
end
def partially_clear_stream_items(locked_by_module: false, section_specific: false)
remaining_participants = participants if section_specific
user_ids = []
stream_item&.stream_item_instances&.shard(stream_item)&.find_each do |item|
if (locked_by_module && locked_by_module_item?(item.user)) ||
(section_specific && remaining_participants.none? { |p| p.id == item.user_id })
destroy_item_and_track(item, user_ids)
end
end
clear_stream_item_cache_for(user_ids)
end
def destroy_item_and_track(item, user_ids)
user_ids.push(item.user_id)
item.destroy
end
def clear_stream_item_cache_for(user_ids)
if stream_item && user_ids.any?
StreamItemCache.delay_if_production(priority: Delayed::LOW_PRIORITY)
.invalidate_all_recent_stream_items(
user_ids,
stream_item.context_type,
stream_item.context_id
)
end
end
def require_initial_post?
require_initial_post || root_topic&.require_initial_post
end
def user_ids_who_have_posted_and_admins
ids = discussion_entries.active.select(:user_id).pluck(:user_id)
ids = ids.uniq
ids += course.admin_enrollments.active.pluck(:user_id) if course.is_a?(Course)
ids
end
def user_can_see_posts?(user, session = nil, associated_user_ids = [])
return false unless user
!require_initial_post? || grants_right?(user, session, :read_as_admin) ||
([user.id] + associated_user_ids).intersect?(user_ids_who_have_posted_and_admins)
end
def locked_announcement?
is_a?(Announcement) && locked?
end
2011-02-01 09:57:29 +08:00
def reply_from(opts)
raise IncomingMail::Errors::ReplyToDeletedDiscussion if deleted?
raise IncomingMail::Errors::UnknownAddress if context.root_account.deleted?
2011-02-01 09:57:29 +08:00
user = opts[:user]
if opts[:html]
message = opts[:html].strip
else
message = opts[:text].strip
message = format_message(message).first
end
2011-02-01 09:57:29 +08:00
user = nil unless user && context.users.include?(user)
if !user
raise IncomingMail::Errors::InvalidParticipant
elsif !grants_right?(user, :read)
nil
2011-02-01 09:57:29 +08:00
else
shard.activate do
entry = discussion_entries.new(message:, user:)
if entry.grants_right?(user, :create) && !comments_disabled? && !locked_announcement?
entry.save!
entry
else
raise IncomingMail::Errors::ReplyToLockedTopic
end
end
2011-02-01 09:57:29 +08:00
end
end
alias_method :destroy_permanently!, :destroy
2011-02-01 09:57:29 +08:00
def destroy
ContentTag.delete_for(self)
self.workflow_state = "deleted"
self.deleted_at = Time.now.utc
Create a table for section specific announcements. This creates a column in discussion_topics called "is_section_specific" and also creates a table for storing which sections go to which announcements. Right now, as long as the section_specific_announcements feature is disabled, it will not be possible to use rails to set this new column to true, or add stuff to the new table, so these tables should be frozen to empty. Closes COMMS-550 Closes COMMS-551 Test Plan: * Have a course with sections and discussion topics in it. * Run "bundle exec rake db:migrate:up VERSION=20171208202812" * Run "bundle exec rake db:migrate:up VERSION=20171208202825" * "rails dbconsole" * Verify that "is_section_specific" is present in the discussion_topics table and that it cannot be null. * Verify that "is_section_specific" has an explicit false value in each row. * Run "bundle exec rake db:migrate:down VERSION=20171208202825" * Run "bundle exec rake db:migrate:down VERSION=20171208202812" * (The order in which you run these migrations matters) * Needless to say, the above stuff should all succeed. * Specs pass. * Make sure that, if the feature is disabled, we can't set the is_section_specific column to true and we can't put stuff in the new table. Change-Id: I2b6e75872a1380a481d2ad15fca3d7e1b257a542 Reviewed-on: https://gerrit.instructure.com/135021 Reviewed-by: Cody Cutrer <cody@instructure.com> Reviewed-by: Steven Burnett <sburnett@instructure.com> Tested-by: Jenkins QA-Review: Felix Milea-Ciobanu <fmileaciobanu@instructure.com> Product-Review: Venk Natarajan <vnatarajan@instructure.com>
2017-12-08 07:51:21 +08:00
discussion_topic_section_visibilities&.update_all(workflow_state: "deleted")
2011-02-01 09:57:29 +08:00
save
if for_assignment? && root_topic_id.blank? && !assignment.deleted?
assignment.skip_downstream_changes! if @skip_downstream_changes
2011-02-01 09:57:29 +08:00
assignment.destroy
end
child_topics.each(&:destroy)
2011-02-01 09:57:29 +08:00
end
def restore(from = nil)
unless restorable?
errors.add(:deleted_at, I18n.t("Cannot undelete a child topic when the root course topic is also deleted. Please undelete the root course topic instead."))
return false
end
if is_section_specific?
DiscussionTopicSectionVisibility.where(discussion_topic_id: id).to_a.uniq(&:course_section_id).each do |dtsv|
dtsv.workflow_state = "active"
dtsv.save
end
end
discussion_topic_section_visibilities.reload
self.workflow_state = can_unpublish? ? "unpublished" : "active"
2011-02-01 09:57:29 +08:00
save
if from != :assignment && for_assignment? && root_topic_id.blank?
2011-02-01 09:57:29 +08:00
assignment.restore(:discussion_topic)
end
Add WikiPage assignment type refs CYOE-67 Test Plan 0. `rake db:migrate` and compile assets. Enable the conditional release feature flag in course settings. 1. Create a published WikiPage, assignment-free. Ensure it is visible to errybody. 2. With the API, create an assignment with submission type 'wiki_page'. A new page will be created for the assignment. ```sh curl "http://canvas.dev:3000/api/v1/courses/1/assignments" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X POST -d '{"assignment": {"name": "Page Asg", "published":true, \ "submission_types":["wiki_page"]}}' ``` 3. Ensure the assignment is assigned to "Everyone" and the page is visible to all. 4. Assign the assignment to a specific student or section. Ensure the page is visible only to the assigned set. ```sh curl "http://canvas.dev:3000/api/v1/courses/1/assignments/13" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X PUT -d '{"assignment": {"only_visible_to_overrides": true }}' curl "http://canvas.dev:3000/api/v1/courses/1/assignments/13/overrides" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X POST -d '{"assignment_override": {"student_ids":[126]}}' ``` 5. Ensure the DA behavior also applies to WikiPage#show and update and edit. 6. Ensure API behavior matches docs for Assignment and Page endpoints. 7. Ensure no regressions in non-page assignments, with and without CR feature flag. Ensure no regressions in discussion topics and announcements, including assigned discussions. Change-Id: Ie53c6ffa4e4533cedd54f10aee3ba0b8a27aa4d5 Reviewed-on: https://gerrit.instructure.com/75139 Reviewed-by: Matt Berns <mberns@instructure.com> Tested-by: Jenkins QA-Review: Michael Hargiss <mhargiss@instructure.com> Product-Review: Christian Prescott <cprescott@instructure.com>
2016-04-08 00:37:16 +08:00
child_topics.each(&:restore)
2011-02-01 09:57:29 +08:00
end
def restorable?
# Not restorable if the root topic context is a course and
# root topic is deleted.
!(root_topic&.context_type == "Course" && root_topic&.deleted?)
end
Add WikiPage assignment type refs CYOE-67 Test Plan 0. `rake db:migrate` and compile assets. Enable the conditional release feature flag in course settings. 1. Create a published WikiPage, assignment-free. Ensure it is visible to errybody. 2. With the API, create an assignment with submission type 'wiki_page'. A new page will be created for the assignment. ```sh curl "http://canvas.dev:3000/api/v1/courses/1/assignments" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X POST -d '{"assignment": {"name": "Page Asg", "published":true, \ "submission_types":["wiki_page"]}}' ``` 3. Ensure the assignment is assigned to "Everyone" and the page is visible to all. 4. Assign the assignment to a specific student or section. Ensure the page is visible only to the assigned set. ```sh curl "http://canvas.dev:3000/api/v1/courses/1/assignments/13" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X PUT -d '{"assignment": {"only_visible_to_overrides": true }}' curl "http://canvas.dev:3000/api/v1/courses/1/assignments/13/overrides" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X POST -d '{"assignment_override": {"student_ids":[126]}}' ``` 5. Ensure the DA behavior also applies to WikiPage#show and update and edit. 6. Ensure API behavior matches docs for Assignment and Page endpoints. 7. Ensure no regressions in non-page assignments, with and without CR feature flag. Ensure no regressions in discussion topics and announcements, including assigned discussions. Change-Id: Ie53c6ffa4e4533cedd54f10aee3ba0b8a27aa4d5 Reviewed-on: https://gerrit.instructure.com/75139 Reviewed-by: Matt Berns <mberns@instructure.com> Tested-by: Jenkins QA-Review: Michael Hargiss <mhargiss@instructure.com> Product-Review: Christian Prescott <cprescott@instructure.com>
2016-04-08 00:37:16 +08:00
def unlink!(type)
2011-02-01 09:57:29 +08:00
@saved_by = type
self.assignment = nil
2011-02-01 09:57:29 +08:00
if discussion_entries.empty?
destroy
2011-02-01 09:57:29 +08:00
else
save
end
Add WikiPage assignment type refs CYOE-67 Test Plan 0. `rake db:migrate` and compile assets. Enable the conditional release feature flag in course settings. 1. Create a published WikiPage, assignment-free. Ensure it is visible to errybody. 2. With the API, create an assignment with submission type 'wiki_page'. A new page will be created for the assignment. ```sh curl "http://canvas.dev:3000/api/v1/courses/1/assignments" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X POST -d '{"assignment": {"name": "Page Asg", "published":true, \ "submission_types":["wiki_page"]}}' ``` 3. Ensure the assignment is assigned to "Everyone" and the page is visible to all. 4. Assign the assignment to a specific student or section. Ensure the page is visible only to the assigned set. ```sh curl "http://canvas.dev:3000/api/v1/courses/1/assignments/13" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X PUT -d '{"assignment": {"only_visible_to_overrides": true }}' curl "http://canvas.dev:3000/api/v1/courses/1/assignments/13/overrides" \ -H "Content-Type: application/json" -H "Authorization: Bearer <token>" \ -X POST -d '{"assignment_override": {"student_ids":[126]}}' ``` 5. Ensure the DA behavior also applies to WikiPage#show and update and edit. 6. Ensure API behavior matches docs for Assignment and Page endpoints. 7. Ensure no regressions in non-page assignments, with and without CR feature flag. Ensure no regressions in discussion topics and announcements, including assigned discussions. Change-Id: Ie53c6ffa4e4533cedd54f10aee3ba0b8a27aa4d5 Reviewed-on: https://gerrit.instructure.com/75139 Reviewed-by: Matt Berns <mberns@instructure.com> Tested-by: Jenkins QA-Review: Michael Hargiss <mhargiss@instructure.com> Product-Review: Christian Prescott <cprescott@instructure.com>
2016-04-08 00:37:16 +08:00
child_topics.each { |t| t.unlink!(:assignment) }
2011-02-01 09:57:29 +08:00
end
2011-02-01 09:57:29 +08:00
def self.per_page
10
end
2011-02-01 09:57:29 +08:00
def initialize_last_reply_at
unless [:migration, :after_migration].include?(saved_by)
self.posted_at ||= Time.now.utc
self.last_reply_at ||= Time.now.utc
end
2011-02-01 09:57:29 +08:00
end
set_policy do
# Users may have can :read, but should not have access to all the data
# because the topic is locked_for?(user)
given { |user| visible_for?(user) }
can :read
2011-02-01 09:57:29 +08:00
given { |user| grants_right?(user, :read) }
can :read_replies
given { |user| self.user && self.user == user && visible_for?(user) && !locked_for?(user, check_policies: true) && can_participate_in_course?(user) }
can :reply
given { |user| self.user && self.user == user && available_for?(user) && context.user_can_manage_own_discussion_posts?(user) && context.grants_right?(user, :participate_as_student) }
can :update
given { |user| self.user && self.user == user and discussion_entries.active.empty? && available_for?(user) && !root_topic_id && context.user_can_manage_own_discussion_posts?(user) && context.grants_right?(user, :participate_as_student) }
can :delete
given do |user, session|
!locked_for?(user, check_policies: true) &&
context.grants_right?(user, session, :post_to_forum) && visible_for?(user) && can_participate_in_course?(user)
end
can :reply
given { |user, session| user_can_create(user, session) }
can :create
given { |user, session| user_can_create(user, session) && user_can_duplicate(user, session) }
can :duplicate
given { |user, session| context.respond_to?(:allow_student_forum_attachments) && context.allow_student_forum_attachments && context.grants_any_right?(user, session, :create_forum, :post_to_forum) }
can :attach
given { course.student_reporting? }
can :student_reporting
given { |user, session| !root_topic_id && context.grants_all_rights?(user, session, :read_forum, :moderate_forum) && available_for?(user) }
can :update and can :read_as_admin and can :delete and can :create and can :read and can :attach
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
# Moderators can still modify content even in unavailable topics (*especially* unlocking them), but can't create new content
given { |user, session| !root_topic_id && context.grants_all_rights?(user, session, :read_forum, :moderate_forum) }
can :update and can :read_as_admin and can :delete and can :read and can :attach
given { |user, session| root_topic&.grants_right?(user, session, :read_as_admin) }
can :read_as_admin
2011-02-01 09:57:29 +08:00
given { |user, session| root_topic&.grants_right?(user, session, :delete) }
can :delete
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
given { |user, session| root_topic&.grants_right?(user, session, :read) }
can :read
given { |user, session| context.grants_all_rights?(user, session, :moderate_forum, :read_forum) }
can :moderate_forum
given do |user, session|
allow_rating && (!only_graders_can_rate ||
course.grants_right?(user, session, :manage_grades))
end
can :rate
2011-02-01 09:57:29 +08:00
end
Add a permission option in the api to return if the user can create topics. closes CNVS-6824 This adds a permissions attribute to the returned json for discussion topic contexts (Course, Group). The permissions attribute contains an optional permission of "create_discission_topic" which returns true or false depending on whether the current user can create discussion topics for the course or group. For performance reasons this is only added to a single course/group json and not in lists so the only call that will return it is /api/v1/<context>/<context_id> where context is a course or group. Since we did not want to include this on every response its a custom permissions attribute for course and groups in the course_json or group_json serialization methods. Using the includes parameter for the API supplying a value of "permissions" will include the permissions with "create_discussion_topic" for a group and course. When the object is serialized to json it checked to see if the model implements a serialize_permissions method and calls that to render or override permissions generated from the policies. - Create a test Course. Make sure the course allows members to post topics. - Add a student to the course. - Make a call to /api/v1/courses/<id> where "<id>" is the id of the created course. - The response should include a permissions attribute with a boolean value for "create_discission_topic" see the example below. - Make a call to /api/v1/courses to return a list of course objects. - The permissions attribute should not be included in the response. - Create a test Group tied to the course created in the first step. - Add a mamber to the group. - Make a call to /api/v1/groups/<id> where "<id>" is the id of the created group. - The response should include a permissions attribute with a boolean value for "create_discission_topic" see the example below. - Make a call to /api/v1/groups to return a list of group objects. - The permissions attribute should not be included in the response. Example Response: { id: 42, ... permissions: { create_discission_topic: true } } Change-Id: Ia02d5aa67e345740a93dd0f63e357e7cb5e1efd6 Reviewed-on: https://gerrit.instructure.com/24478 Reviewed-by: Jacob Fugal <jacob@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> QA-Review: August Thornton <august@instructure.com> Product-Review: Nick Cloward <ncloward@instructure.com>
2013-09-18 06:24:57 +08:00
def self.context_allows_user_to_create?(context, user, session)
new(context:).grants_right?(user, session, :create)
Add a permission option in the api to return if the user can create topics. closes CNVS-6824 This adds a permissions attribute to the returned json for discussion topic contexts (Course, Group). The permissions attribute contains an optional permission of "create_discission_topic" which returns true or false depending on whether the current user can create discussion topics for the course or group. For performance reasons this is only added to a single course/group json and not in lists so the only call that will return it is /api/v1/<context>/<context_id> where context is a course or group. Since we did not want to include this on every response its a custom permissions attribute for course and groups in the course_json or group_json serialization methods. Using the includes parameter for the API supplying a value of "permissions" will include the permissions with "create_discussion_topic" for a group and course. When the object is serialized to json it checked to see if the model implements a serialize_permissions method and calls that to render or override permissions generated from the policies. - Create a test Course. Make sure the course allows members to post topics. - Add a student to the course. - Make a call to /api/v1/courses/<id> where "<id>" is the id of the created course. - The response should include a permissions attribute with a boolean value for "create_discission_topic" see the example below. - Make a call to /api/v1/courses to return a list of course objects. - The permissions attribute should not be included in the response. - Create a test Group tied to the course created in the first step. - Add a mamber to the group. - Make a call to /api/v1/groups/<id> where "<id>" is the id of the created group. - The response should include a permissions attribute with a boolean value for "create_discission_topic" see the example below. - Make a call to /api/v1/groups to return a list of group objects. - The permissions attribute should not be included in the response. Example Response: { id: 42, ... permissions: { create_discission_topic: true } } Change-Id: Ia02d5aa67e345740a93dd0f63e357e7cb5e1efd6 Reviewed-on: https://gerrit.instructure.com/24478 Reviewed-by: Jacob Fugal <jacob@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> QA-Review: August Thornton <august@instructure.com> Product-Review: Nick Cloward <ncloward@instructure.com>
2013-09-18 06:24:57 +08:00
end
def context_allows_user_to_create?(user)
return true unless context.respond_to?(:allow_student_discussion_topics)
return true if context.grants_right?(user, :read_as_admin)
context.allow_student_discussion_topics
end
def user_can_create(user, session)
!is_announcement &&
context.grants_right?(user, session, :create_forum) &&
context_allows_user_to_create?(user)
end
def user_can_duplicate(user, session)
context.is_a?(Group) ||
course.user_is_instructor?(user) ||
context.grants_right?(user, session, :read_as_admin)
end
2011-02-01 09:57:29 +08:00
def discussion_topic_id
id
end
2011-02-01 09:57:29 +08:00
def discussion_topic
self
end
2011-02-01 09:57:29 +08:00
def to_atom(opts = {})
author_name = user.present? ? user.name : t("#discussion_topic.atom_no_author", "No Author")
prefix = [is_announcement ? t("#titles.announcement", "Announcement") : t("#titles.discussion", "Discussion")]
prefix << context.name if opts[:include_context]
2011-02-01 09:57:29 +08:00
Atom::Entry.new do |entry|
entry.title = [before_label(prefix.to_sentence), title].join(" ")
entry.authors << Atom::Person.new(name: author_name)
2011-02-01 09:57:29 +08:00
entry.updated = updated_at
entry.published = created_at
entry.id = "tag:#{HostUrl.default_host},#{created_at.strftime("%Y-%m-%d")}:/discussion_topics/#{feed_code}"
entry.links << Atom::Link.new(rel: "alternate",
2011-02-01 09:57:29 +08:00
href: "http://#{HostUrl.context_host(context)}/#{context_url_prefix}/discussion_topics/#{id}")
entry.content = Atom::Content::Html.new(message || "")
end
end
2011-02-01 09:57:29 +08:00
def context_prefix
context_url_prefix
end
2011-02-01 09:57:29 +08:00
def context_module_action(user, action, points = nil)
return root_topic.context_module_action(user, action, points) if root_topic
allow using an item in modules more than once closes #8769 An item can be added to multiple modules, or even the same module more than once. This is especially useful for attachment items, but is also useful for allowing multiple paths through a course, with say an assignment in two different modules and the user only has to complete one of the two modules. test plan: For an item in only one module, verify that the module navigation still appears if you go straight to that item's page, without going through the modules page. Add an item to more than one module. If you visit that item from the modules page, you'll see the right nav depending on which instance of the item you clicked on. If you visit the item directly without going through the modules page, you'll see no nav. Lock one instance of the item by adding a prerequisite, but leave the other unlocked. You can still see the item as a student. Lock all instances of the item with prerequisites. The item will now be locked and you can't see it as a student. Add completion requirements to the item, such as a minimum score on a quiz. Make the requirements different -- 3 points in one instance and 5 in the other, for instance. Verify that if you get 3 points on the quiz, one item is marked as completed but the other isn't, as expected. Rename the item. Verify that all instances of it in modules get renamed. Change-Id: I4f1b2f6f033062ec47ac34fe5eb973a950c17b0c Reviewed-on: https://gerrit.instructure.com/11671 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Bracken Mosbacker <bracken@instructure.com>
2012-06-19 06:18:43 +08:00
tags_to_update = context_module_tags.to_a
if for_assignment?
allow using an item in modules more than once closes #8769 An item can be added to multiple modules, or even the same module more than once. This is especially useful for attachment items, but is also useful for allowing multiple paths through a course, with say an assignment in two different modules and the user only has to complete one of the two modules. test plan: For an item in only one module, verify that the module navigation still appears if you go straight to that item's page, without going through the modules page. Add an item to more than one module. If you visit that item from the modules page, you'll see the right nav depending on which instance of the item you clicked on. If you visit the item directly without going through the modules page, you'll see no nav. Lock one instance of the item by adding a prerequisite, but leave the other unlocked. You can still see the item as a student. Lock all instances of the item with prerequisites. The item will now be locked and you can't see it as a student. Add completion requirements to the item, such as a minimum score on a quiz. Make the requirements different -- 3 points in one instance and 5 in the other, for instance. Verify that if you get 3 points on the quiz, one item is marked as completed but the other isn't, as expected. Rename the item. Verify that all instances of it in modules get renamed. Change-Id: I4f1b2f6f033062ec47ac34fe5eb973a950c17b0c Reviewed-on: https://gerrit.instructure.com/11671 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Bracken Mosbacker <bracken@instructure.com>
2012-06-19 06:18:43 +08:00
tags_to_update += assignment.context_module_tags
if context.grants_right?(user, :participate_as_student) && assignment.visible_to_user?(user) && [:contributed, :deleted].include?(action)
only_update = (action == :deleted) # if we're deleting an entry, don't make a submission if it wasn't there already
ensure_submission(user, only_update)
end
end
unless action == :deleted
tags_to_update.each { |tag| tag.context_module_action(user, action, points) }
end
end
def ensure_submission(user, only_update = false)
topic = (root_topic? && child_topic_for(user)) || self
submissions = []
all_entries_for_user = topic.discussion_entries.all_for_user(user)
if topic.root_account&.feature_enabled?(:discussion_checkpoints) && checkpoints?
reply_to_topic_submitted_at = topic.discussion_entries.top_level_for_user(user).minimum(:created_at)
if reply_to_topic_submitted_at.present?
reply_to_topic_submission = ensure_particular_submission(reply_to_topic_checkpoint, user, reply_to_topic_submitted_at, only_update:)
submissions << reply_to_topic_submission if reply_to_topic_submission.present?
end
reply_to_entry_submitted_at = topic.discussion_entries.non_top_level_for_user(user).minimum(:created_at)
if reply_to_entry_submitted_at.present?
reply_to_entry_submission = ensure_particular_submission(reply_to_entry_checkpoint, user, reply_to_entry_submitted_at, only_update:)
submissions << reply_to_entry_submission if reply_to_entry_submission.present?
end
else
submitted_at = all_entries_for_user.minimum(:created_at)
submission = ensure_particular_submission(assignment, user, submitted_at, only_update:)
submissions << submission if submission.present?
end
return unless submissions.any?
attachment_ids = all_entries_for_user.where.not(attachment_id: nil).pluck(:attachment_id).sort.map(&:to_s).join(",")
submissions.each do |s|
s.attachment_ids = attachment_ids
s.save! if s.changed?
end
end
def ensure_particular_submission(assignment, user, submitted_at, only_update: false)
submission = Submission.active.where(assignment_id: assignment.id, user_id: user).first
unless only_update || (submission && submission.submission_type == "discussion_topic" && submission.workflow_state != "unsubmitted")
submission = assignment.submit_homework(user,
submission_type: "discussion_topic",
submitted_at:)
end
submission
2011-02-01 09:57:29 +08:00
end
def send_notification_for_context?
notification_context =
if context.is_a?(Group) && context.context.is_a?(Course)
context.context # we need to go deeper
else
context
end
notification_context.available?
end
def course_broadcast_data
context&.broadcast_data
end
2011-02-01 09:57:29 +08:00
has_a_broadcast_policy
set_broadcast_policy do |p|
p.dispatch :new_discussion_topic
p.to { users_with_permissions(active_participants_with_visibility) }
2011-02-01 09:57:29 +08:00
p.whenever do |record|
record.send_notification_for_context? and
((record.just_created && record.active?) || record.changed_state(:active, record.is_announcement ? :post_delayed : :unpublished))
end
p.data { course_broadcast_data }
2011-02-01 09:57:29 +08:00
end
2011-02-01 09:57:29 +08:00
def delay_posting=(val); end
2011-02-01 09:57:29 +08:00
def set_assignment=(val); end
# From the given list of users, return those that are permitted to see the section
# of the topic. If the topic is not section specific this just returns the
# original list.
def users_with_section_visibility(users)
return users unless is_section_specific? && context.is_a?(Course)
non_nil_users = users.compact
section_ids = DiscussionTopicSectionVisibility.active.where(discussion_topic_id: id)
.pluck(:course_section_id)
user_ids = non_nil_users.pluck(:id)
# Context is known to be a course here
users_in_sections = context.enrollments.active_or_pending
.where(user_id: user_ids, course_section_id: section_ids).pluck(:user_id).to_set
unlocked_teachers = context.enrollments.active_or_pending.instructor
.where(limit_privileges_to_course_section: false, user_id: user_ids)
.pluck(:user_id).to_set
permitted_user_ids = users_in_sections.union(unlocked_teachers)
non_nil_users.select { |u| permitted_user_ids.include?(u.id) }
end
def participants(include_observers = false)
participants = context.participants(include_observers:, by_date: true)
participants_in_section = users_with_section_visibility(participants.compact)
if user && !participants_in_section.to_set(&:id).include?(user.id)
participants_in_section += [user]
end
participants_in_section
2011-02-01 09:57:29 +08:00
end
def visible_to_admins_only?
(context.respond_to?(:available?) && !context.available?) ||
unpublished? || not_available_yet? || not_available_anymore?
end
def active_participants(include_observers = false)
if visible_to_admins_only? && context.respond_to?(:participating_admins)
context.participating_admins
else
participants(include_observers)
end
end
def active_participants_include_tas_and_teachers(include_observers = false)
participants = active_participants(include_observers)
if context.is_a?(Group) && !context.course.nil?
participants += context.course.participating_instructors_by_date
participants = participants.compact.uniq
end
participants
end
def users_with_permissions(users)
permission = is_announcement ? :read_announcements : :read_forum
course = self.course
unless course.is_a?(Course)
return users.select do |u|
is_announcement ? context.grants_right?(u, :read_announcements) : context.grants_right?(u, :read_forum)
end
end
readers = self.course.filter_users_by_permission(users, permission)
users_with_section_visibility(readers)
end
def course
@course ||= context.is_a?(Group) ? context.context : context
end
def group
@group ||= context.is_a?(Group) ? context : nil
end
def active_participants_with_visibility
return active_participants_include_tas_and_teachers unless for_assignment?
users_with_visibility = assignment.students_with_visibility.pluck(:id)
admin_ids = course.participating_admins.pluck(:id)
users_with_visibility.concat(admin_ids)
# observers will not be returned, which is okay for the functions current use cases (but potentially not others)
active_participants_include_tas_and_teachers.select { |p| users_with_visibility.include?(p.id) }
end
def participating_users(user_ids)
context.respond_to?(:participating_users) ? context.participating_users(user_ids) : User.find(user_ids)
end
def subscribers
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
# this duplicates some logic from #subscribed? so we don't have to call
# #posters for each legacy subscriber.
sub_ids = discussion_topic_participants.where(subscribed: true).pluck(:user_id)
legacy_sub_ids = discussion_topic_participants.where(subscribed: nil).pluck(:user_id)
poster_ids = posters.map(&:id)
legacy_sub_ids &= poster_ids
sub_ids += legacy_sub_ids
subscribed_users = participating_users(sub_ids).to_a
filter_message_users(subscribed_users)
end
def filter_message_users(users)
if for_assignment?
students_with_visibility = assignment.students_with_visibility.pluck(:id)
admin_ids = course.participating_admins.pluck(:id)
observer_ids = course.participating_observers.pluck(:id)
observed_students = ObserverEnrollment.observed_student_ids_by_observer_id(course, observer_ids)
users.select! do |user|
students_with_visibility.include?(user.id) || admin_ids.include?(user.id) ||
# an observer with no students or one with students who have visibility
(observed_students[user.id] && (observed_students[user.id] == [] || observed_students[user.id].intersect?(students_with_visibility)))
end
end
users
end
2011-02-01 09:57:29 +08:00
def posters
user_ids = discussion_entries.map(&:user_id).push(user_id).uniq
participating_users(user_ids)
2011-02-01 09:57:29 +08:00
end
def user_name
user&.name
2011-02-01 09:57:29 +08:00
end
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
def available_from_for(user)
if assignment
assignment.overridden_for(user).unlock_at
else
available_from
end
end
def available_for?(user, opts = {})
return false unless published?
return false if is_announcement && locked?
discussion draft state backend fixes CNVS-9263 test plan be sure to test ungraded discussions, graded discussions, graded group discussions (root level and group topics), and announcements with draft state disabled - ensure publish icons do not appear on discussion index page - ensure publish button does not appear on discussion show page - ensure that discussions for delayed posting are not visible to students with draft state enabled - ensure that publish icons appear on the discussion index page and that clicking them toggles the discussion's publish state - ensure that publish button appears on the discussion show page and that clicking it toggles the discusssion's publish state - ensure that unpublished topics are not visible to students - ensure that published topics are visible to students, even if the topic is for delayed posting - ensure that topics for delayed posting show a 'This topic will open on' message on the topic show page - ensure that [un]publishing the root topic of a graded group discussion changes the group level discussion and vice versa - ensure that announcements are published when posted and cannot be unpublished test plan for misc frontend fixes - as a teacher, create a new discussion - ensure it is unpublished - as a student, create a new discussion - ensure that it is published and that no publish button appears on the topic show page or the topic index page - as a teacher, create a new graded group discussion - navigate to the group level topic - publish the topic using the button on the show page - ensure that the button switches to the 'Published' state - as a teacher, create an announcement - ensure that it is published Change-Id: If0c46d232bc25776770012654af23e5cf10bbb45 Reviewed-on: https://gerrit.instructure.com/26654 QA-Review: Joel Hough <joel@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> Product-Review: Clare Strong <clare@instructure.com>
2013-11-25 19:58:38 +08:00
!locked_for?(user, opts)
end
# Public: Determine if the given user can view this discussion topic.
#
# user - The user attempting to view the topic (default: nil).
#
# Returns a boolean.
def visible_for?(user = nil)
RequestCache.cache("discussion_visible_for", self, is_announcement, user) do
# user is the topic's author
next true if user && user.id == user_id
next false unless context
next false unless is_announcement ? context.grants_right?(user, :read_announcements) : context.grants_right?(user, :read_forum)
Fix teachers interacting with the wrong sections Fixes COMMS-986 Test Plan: * Turn on SSA/SSD * Create a course with multiple sections * Create a teacher locked to one section, and another teacher not locked to a section * Create some announcements and discussions that are in various sections * As the teacher who is not locked to a section, make sure that you can see all discussions or announcements in all sections - Course home page - Announcements index page - Discussions index page - Announcement view page - Discussion view page - Announcement edit page - Discussion edit page * As the teacher who is locked to a section, make sure that you can not see any discussions in other sections (on the course home - Course home page - Announcements index page - Discussions index page - Announcement view page http://127.0.0.1:3000/courses/1/discussion_topics/1672 - Discussion view page http://127.0.0.1:3000/courses/1/discussion_topics/1672 - Announcement edit page http://127.0.0.1:3000/courses/1/discussion_topics/1672/edit - Discussion edit page http://127.0.0.1:3000/courses/1/discussion_topics/1672/edit * Make sure the groups discussion and announcement pages still work * Make sure that discussions still work with the feature flag turned off (smoke test) Change-Id: I54e7140844a69fe3930d82ba45ade6fb3b5b7f55 Reviewed-on: https://gerrit.instructure.com/144413 Reviewed-by: Venk Natarajan <vnatarajan@instructure.com> Reviewed-by: Steven Burnett <sburnett@instructure.com> Tested-by: Jenkins QA-Review: Venk Natarajan <vnatarajan@instructure.com> Product-Review: Venk Natarajan <vnatarajan@instructure.com>
2018-03-22 05:09:55 +08:00
# Don't have visibilites for any of the specific sections in a section specific topic
if context.is_a?(Course) && try(:is_section_specific)
section_visibilities = context.course_section_visibility(user)
next false if section_visibilities == :none
Fix teachers interacting with the wrong sections Fixes COMMS-986 Test Plan: * Turn on SSA/SSD * Create a course with multiple sections * Create a teacher locked to one section, and another teacher not locked to a section * Create some announcements and discussions that are in various sections * As the teacher who is not locked to a section, make sure that you can see all discussions or announcements in all sections - Course home page - Announcements index page - Discussions index page - Announcement view page - Discussion view page - Announcement edit page - Discussion edit page * As the teacher who is locked to a section, make sure that you can not see any discussions in other sections (on the course home - Course home page - Announcements index page - Discussions index page - Announcement view page http://127.0.0.1:3000/courses/1/discussion_topics/1672 - Discussion view page http://127.0.0.1:3000/courses/1/discussion_topics/1672 - Announcement edit page http://127.0.0.1:3000/courses/1/discussion_topics/1672/edit - Discussion edit page http://127.0.0.1:3000/courses/1/discussion_topics/1672/edit * Make sure the groups discussion and announcement pages still work * Make sure that discussions still work with the feature flag turned off (smoke test) Change-Id: I54e7140844a69fe3930d82ba45ade6fb3b5b7f55 Reviewed-on: https://gerrit.instructure.com/144413 Reviewed-by: Venk Natarajan <vnatarajan@instructure.com> Reviewed-by: Steven Burnett <sburnett@instructure.com> Tested-by: Jenkins QA-Review: Venk Natarajan <vnatarajan@instructure.com> Product-Review: Venk Natarajan <vnatarajan@instructure.com>
2018-03-22 05:09:55 +08:00
if section_visibilities != :all
course_specific_sections = course_sections.pluck(:id)
next false unless section_visibilities.intersect?(course_specific_sections)
Fix teachers interacting with the wrong sections Fixes COMMS-986 Test Plan: * Turn on SSA/SSD * Create a course with multiple sections * Create a teacher locked to one section, and another teacher not locked to a section * Create some announcements and discussions that are in various sections * As the teacher who is not locked to a section, make sure that you can see all discussions or announcements in all sections - Course home page - Announcements index page - Discussions index page - Announcement view page - Discussion view page - Announcement edit page - Discussion edit page * As the teacher who is locked to a section, make sure that you can not see any discussions in other sections (on the course home - Course home page - Announcements index page - Discussions index page - Announcement view page http://127.0.0.1:3000/courses/1/discussion_topics/1672 - Discussion view page http://127.0.0.1:3000/courses/1/discussion_topics/1672 - Announcement edit page http://127.0.0.1:3000/courses/1/discussion_topics/1672/edit - Discussion edit page http://127.0.0.1:3000/courses/1/discussion_topics/1672/edit * Make sure the groups discussion and announcement pages still work * Make sure that discussions still work with the feature flag turned off (smoke test) Change-Id: I54e7140844a69fe3930d82ba45ade6fb3b5b7f55 Reviewed-on: https://gerrit.instructure.com/144413 Reviewed-by: Venk Natarajan <vnatarajan@instructure.com> Reviewed-by: Steven Burnett <sburnett@instructure.com> Tested-by: Jenkins QA-Review: Venk Natarajan <vnatarajan@instructure.com> Product-Review: Venk Natarajan <vnatarajan@instructure.com>
2018-03-22 05:09:55 +08:00
end
end
# user is an admin in the context (teacher/ta/designer) OR
# user is an account admin with appropriate permission
next true if context.grants_any_right?(user, :manage, :read_course_content)
add section specific logic to announcements + discussions api refs COMMS-587 COMMS-588 test plan: - enable the section specific announcements feature flag - use a course with multiple sections and students across sections - create some section specific announcements and regular announcements - test the following API routes as both a teacher and a student - use your own course id as appropriate - as a teacher you should see all announcements in a course - as a student you should only see announcements specific to the sections that you are enrolled in [Announcements API] - /api/v1/announcements?context_codes=course_2 - note that each announcement has a "is_section_specific" property with the correct value - /api/v1/announcements?context_codes=course_2&include[]=sections - note that section specific announcements include correct section - /api/v1/announcements?context_codes=course_2&include[]=sections&include[]=sections_student_count - note that section specific announcements' include sections with correct "student_count" property [Discussion Topics API] - /api/v1/courses/2/discussion_topics/?only_announcements=true - note that each announcement has a "is_section_specific" property with the correct value - /api/v1/courses/2/discussion_topics/?only_announcements=true&include[]=sections - note that section specific announcements include correct section - /api/v1/courses/2/discussion_topics/?only_announcements=true&include[]=sections&include[]=sections_studenst_count - note that section specific announcements' include sections with correct "student_count" property Change-Id: Idbe68e2d72a538ce5d3701daf12287f3e8bb8d94 Reviewed-on: https://gerrit.instructure.com/136266 Reviewed-by: Steven Burnett <sburnett@instructure.com> Tested-by: Jenkins Reviewed-by: Venk Natarajan <vnatarajan@instructure.com> QA-Review: Landon Gilbert-Bland <lbland@instructure.com> Product-Review: Felix Milea-Ciobanu <fmileaciobanu@instructure.com>
2017-12-20 21:26:17 +08:00
# assignment exists and isn't assigned to user (differentiated assignments)
if for_assignment? && !assignment.visible_to_user?(user)
next false
end
# topic is not published
if !published?
next false
elsif is_announcement && (unlock_at = available_from_for(user))
# unlock date exists and has passed
next unlock_at < Time.now.utc
# everything else
else
next true
end
end
end
def can_participate_in_course?(user)
if group&.deleted?
false
elsif course.is_a?(Course)
# this probably isn't a perfect way to determine this but I can't think of a better one
course.enrollments.for_user(user).active_by_date.exists? || course.grants_right?(user, :read_as_admin)
else
true
end
end
# Determine if the discussion topic is locked for a user. The topic is locked
# if the delayed_post_at is in the future or the assignment is locked.
# This does not determine the visibility of the topic to the user,
# only that they are unable to reply and unable to see the message.
# Generally you want to call :locked_for?(user, check_policies: true), which
# will call this method.
def low_level_locked_for?(user, opts = {})
return false if opts[:check_policies] && grants_right?(user, :read_as_admin)
RequestCache.cache(locked_request_cache_key(user)) do
2011-02-01 09:57:29 +08:00
locked = false
if delayed_post_at && delayed_post_at > Time.now
locked = { object: self, unlock_at: delayed_post_at }
added auto lock discussions on specified date fixes CNVS-4106 test steps: - go to create a discussion topic and ensure that you can toggle between the 'Graded' and non-graded availability dates. If the Graded is not checked, you should see the Available From/Until. - create a discussion topic with a past from and past until. ensure that the discussion is locked for a student - create a discussion topic with a future from and future until. ensure the discussion cannot be seen by the student - update the above created discussion topic and switch it to be graded and set a future due, from, until date. ensure that the student cannot see the discussion. - edit the discussion and ensure that the graded checkbox is checked and the group assignment fields are showing - update the assignment with a past due, from, until and make sure the discussion is locked for the student. - do all above with current dates (from in past, until in future) and make sure the student can see and reply to them. - create an announcement and ensure that the functionality and forms are the same as before - verify that you are able to lock and unlock discussion topics on the show view by using the gear drop-down Change-Id: I99c9d54763fe3a74aa8a4bb37c22f09d4765d41e Reviewed-on: https://gerrit.instructure.com/20339 Tested-by: Jenkins <jenkins@instructure.com> Reviewed-by: Jon Willesen <jonw@instructure.com> Reviewed-by: Zach Pendleton <zachp@instructure.com> QA-Review: Marc LeGendre <marc@instructure.com> Product-Review: Marc LeGendre <marc@instructure.com>
2013-05-03 07:39:26 +08:00
elsif lock_at && lock_at < Time.now
locked = { object: self, lock_at:, can_view: true }
elsif !opts[:skip_assignment] && (l = assignment&.low_level_locked_for?(user, opts))
2011-02-01 09:57:29 +08:00
locked = l
elsif could_be_locked && (item = locked_by_module_item?(user, opts))
locked = { object: self, module: item.context_module }
elsif locked? # nothing more specific, it's just locked
locked = { object: self, can_view: true }
elsif (l = root_topic&.low_level_locked_for?(user, opts)) # rubocop:disable Lint/DuplicateBranch
2011-02-01 09:57:29 +08:00
locked = l
end
locked
end
end
def self.reject_context_module_locked_topics(topics, user)
progressions = ContextModuleProgression
.joins(context_module: :content_tags)
.where({
:user => user,
"content_tags.content_type" => "DiscussionTopic",
"content_tags.content_id" => topics,
})
.select("context_module_progressions.*")
.distinct_on("context_module_progressions.id")
.preload(:user)
progressions = progressions.index_by(&:context_module_id)
topics.reject do |topic|
topic.locked_by_module_item?(user, {
deep_check_if_needed: true,
user_context_module_progressions: progressions,
})
end
end
def entries_for_feed(user, podcast_feed = false)
return [] unless user_can_see_posts?(user)
return [] if locked_for?(user, check_policies: true)
entries = discussion_entries.active
if podcast_feed && !podcast_has_student_posts && context.is_a?(Course)
entries = entries.where(user_id: context.admins)
end
entries
end
def self.podcast_elements(messages, context)
2011-02-01 09:57:29 +08:00
attachment_ids = []
media_object_ids = []
messages_hash = {}
messages.each do |message|
txt = (message.message || "")
2011-02-01 09:57:29 +08:00
attachment_matches = txt.scan(%r{/#{context.class.to_s.pluralize.underscore}/#{context.id}/files/(\d+)/download})
attachment_ids += (attachment_matches || []).pluck(0)
media_object_matches = txt.scan(/media_comment_([\w-]+)/) + txt.scan(/data-media-id="([\w-]+)"/)
media_object_ids += (media_object_matches || []).pluck(0).uniq
2011-02-01 09:57:29 +08:00
(attachment_ids + media_object_ids).each do |id|
messages_hash[id] ||= message
2011-02-01 09:57:29 +08:00
end
end
2011-02-01 09:57:29 +08:00
media_object_ids = media_object_ids.uniq.compact
attachment_ids = attachment_ids.uniq.compact
attachments = attachment_ids.empty? ? [] : context.attachments.active.find_all_by_id(attachment_ids)
2011-02-01 09:57:29 +08:00
attachments = attachments.select { |a| a.content_type&.match(/(video|audio)/) }
attachments.each do |attachment|
attachment.podcast_associated_asset = messages_hash[attachment.id.to_s]
2011-02-01 09:57:29 +08:00
end
media_object_ids -= attachments.filter_map(&:media_entry_id) # don't include media objects if the file is already included
media_objects = media_object_ids.empty? ? [] : MediaObject.where(media_id: media_object_ids).to_a
media_objects = media_objects.uniq(&:media_id)
2011-02-01 09:57:29 +08:00
media_objects = media_objects.map do |media_object|
if media_object.media_id == "maybe" || media_object.deleted? || (media_object.context_type != "User" && media_object.context != context)
2011-02-01 09:57:29 +08:00
media_object = nil
end
if media_object&.podcast_format_details
media_object.podcast_associated_asset = messages_hash[media_object.media_id]
2011-02-01 09:57:29 +08:00
end
media_object
end
2011-02-01 09:57:29 +08:00
to_podcast(attachments + media_objects.compact)
end
def self.to_podcast(elements)
2011-02-01 09:57:29 +08:00
require "rss/2.0"
elements.filter_map do |elem|
asset = elem.podcast_associated_asset
next unless asset
2011-02-01 09:57:29 +08:00
item = RSS::Rss::Channel::Item.new
item.title = before_label((asset.title rescue "")) + elem.name
link = nil
case asset
when DiscussionTopic
link = "http://#{HostUrl.context_host(asset.context)}/#{asset.context_url_prefix}/discussion_topics/#{asset.id}"
when DiscussionEntry
link = "http://#{HostUrl.context_host(asset.context)}/#{asset.context_url_prefix}/discussion_topics/#{asset.discussion_topic_id}#entry-#{asset.id}"
end
2011-02-01 09:57:29 +08:00
item.link = link
item.guid = RSS::Rss::Channel::Item::Guid.new
item.pubDate = elem.updated_at.utc
item.description = asset ? asset.message : elem.name
2011-02-01 09:57:29 +08:00
item.enclosure
case elem
when Attachment
item.guid.content = link + "/#{elem.uuid}"
url = "http://#{HostUrl.context_host(elem.context)}/#{elem.context_url_prefix}" \
"/files/#{elem.id}/download#{elem.extension}?verifier=#{elem.uuid}"
item.enclosure = RSS::Rss::Channel::Item::Enclosure.new(url, elem.size, elem.content_type)
2011-02-01 09:57:29 +08:00
when MediaObject
item.guid.content = link + "/#{elem.media_id}"
details = elem.podcast_format_details
content_type = "video/mpeg"
content_type = "audio/mpeg" if elem.media_type == "audio"
size = details[:size].to_i.kilobytes
ext = details[:extension] || details[:fileExt]
url = "http://#{HostUrl.context_host(elem.context)}/#{elem.context_url_prefix}" \
"/media_download.#{ext}?type=#{ext}&entryId=#{elem.media_id}&redirect=1"
item.enclosure = RSS::Rss::Channel::Item::Enclosure.new(url, size, content_type)
2011-02-01 09:57:29 +08:00
end
item
end
2011-02-01 09:57:29 +08:00
end
def initial_post_required?(user, session = nil)
if require_initial_post?
associated_user_ids = user.observer_enrollments.active.where(course_id: course).pluck(:associated_user_id).compact
return !user_can_see_posts?(user, session, associated_user_ids)
end
false
end
# returns the materialized view of the discussion as structure, participant_ids, and entry_ids
# the view is already converted to a json string, the other two arrays of ids are ruby arrays
# see the description of the format in the discussion topics api documentation.
#
# returns nil if the view is not currently available, and kicks off a
# background job to build the view. this typically only takes a couple seconds.
#
# if a new message is posted, it won't appear in this view until the job to
# update it completes. so this view is eventually consistent.
#
# if the topic itself is not yet created, it will return blank data. this is for situations
# where we're creating topics on the first write - until that first write, we need to return
# blank data on reads.
def materialized_view(opts = {})
if new_record?
["[]", [], [], []]
else
DiscussionTopic::MaterializedView.materialized_view_for(self, opts)
end
end
# synchronously create/update the materialized view
def create_materialized_view
DiscussionTopic::MaterializedView.for(self).update_materialized_view(synchronous: true, use_master: true)
end
fix default grading scheme for letter grade assignment For letter grade assignments and discussion topics, the default grading scheme is now 1. the course grading scheme, if it is using one, or 2. the Canvas default grading scheme. Previously, the default grading scheme for letter grade assignments and discussion topics was always the Canvas default grading scheme. closes GRADE-914 refs CNVS-15228 Test Plan 1: Assignments 1. Set up a course that is NOT using a grading scheme. 2. Create a new assignment, and verify the default Grading Scheme when choosing 'Letter Grade' is the Canvas Default (A -> F). 3. Go to the New Gradebook and verify you can enter grades in the submission cells and Grade Details Tray input according to the Canvas default Grading Scheme rules (i.e. you can enter 'A' or 'B' or 'C', etc.) 4. Go to edit the assignment, and verify when you click 'Letter Grade' the Canvas Default Grading Scheme is shown. 5. Create a new custom grading scheme at courses/:id/grading_standards. I'll create one that looks like 'Hi' -> 100 to 94%, 'Bye' -> < 94 to 0%. 6. At the course settings page, check the box next to 'Enable course grading scheme' and select the custom scheme created in step 5. Click 'Update Course Details' at the bottom of the page. 7. Create a new assignment, and verify the default Grading Scheme when choosing 'Letter Grade' is the custom scheme. 8. Go to the New Gradebook and verify you can enter grades in the submission cells and Grade Details Tray input according to the custom grading scheme rules (i.e. you can enter 'Hi' or 'Bye', but entering 'A' or 'B' shows an invalid input error). 9. Go to edit the assignment, and verify when you click 'Letter Grade' the custom grading scheme is shown. Test Plan 2: Graded Discussion Topics 1. Set up a course that is NOT using a grading scheme. 2. Create a new graded discussion topic, and verify the default Grading Scheme when choosing 'Letter Grade' is the Canvas Default (A -> F). 3. Go to the New Gradebook and verify you can enter grades in the submission cells and Grade Details Tray input according to the Canvas default Grading Scheme rules (i.e. you can enter 'A' or 'B' or 'C', etc.) 4. Go to edit the discussion topic, and verify when you click 'Letter Grade' the Canvas Default Grading Scheme is shown. 5. Create a new custom grading scheme at courses/:id/grading_standards. I'll create one that looks like 'Hi' -> 100 to 94%, 'Bye' -> < 94 to 0%. 6. At the course settings page, check the box next to 'Enable course grading scheme' and select the custom scheme created in step 5. Click 'Update Course Details' at the bottom of the page. 7. Create a new graded discussion topic, and verify the default Grading Scheme when choosing 'Letter Grade' is the custom scheme. 8. Go to the New Gradebook and verify you can enter grades in the submission cells and Grade Details Tray input according to the custom grading scheme rules (i.e. you can enter 'Hi' or 'Bye', but entering 'A' or 'B' shows an invalid input error). 9. Go to edit the discussion topic, and verify when you click 'Letter Grade' the custom grading scheme is shown. Change-Id: I0b7a7bd1f18a506be83a923a421a45e96d129c96 Reviewed-on: https://gerrit.instructure.com/142105 Reviewed-by: Jeremy Neander <jneander@instructure.com> Reviewed-by: Shahbaz Javeed <sjaveed@instructure.com> Tested-by: Jenkins QA-Review: Indira Pai <ipai@instructure.com> Product-Review: Matt Goodwin <mattg@instructure.com>
2018-02-28 07:06:54 +08:00
def grading_standard_or_default
grading_standard_context = assignment || context
if grading_standard_context.present?
grading_standard_context.grading_standard_or_default
else
GradingStandard.default_instance
end
end
def set_root_account_id
self.root_account_id ||= context&.root_account_id
end
def anonymous?
!anonymous_state.nil?
end
def checkpoints?
checkpoint_assignments.any?
end
def reply_to_topic_checkpoint
checkpoint_assignments.find_by(checkpoint_label: CheckpointLabels::REPLY_TO_TOPIC)
end
def reply_to_entry_checkpoint
checkpoint_assignments.find_by(checkpoint_label: CheckpointLabels::REPLY_TO_ENTRY)
end
def create_checkpoints(reply_to_topic_points:, reply_to_entry_points:)
return false if checkpoints?
return false unless context.is_a?(Course)
parent = context.assignments.create!(checkpointed: true, checkpoint_label: CheckpointLabels::PARENT)
parent.checkpoint_assignments.create!(context:, checkpoint_label: CheckpointLabels::REPLY_TO_TOPIC, points_possible: reply_to_topic_points)
parent.checkpoint_assignments.create!(context:, checkpoint_label: CheckpointLabels::REPLY_TO_ENTRY, points_possible: reply_to_entry_points)
self.assignment = parent
save
end
2011-02-01 09:57:29 +08:00
end