+ );
+ }
+}
diff --git a/app/assets/javascripts/fontawesome_config.js b/app/assets/javascripts/fontawesome_config.js
index aee577c46a..ba622ad2a3 100644
--- a/app/assets/javascripts/fontawesome_config.js
+++ b/app/assets/javascripts/fontawesome_config.js
@@ -22,6 +22,7 @@ import {
faFileImage,
faFileImport,
faFilePdf,
+ faFilter,
faFolder,
faFolderOpen,
faFolderPlus,
@@ -49,6 +50,8 @@ import {
import {faGithub} from "@fortawesome/free-brands-svg-icons";
+import {faSquare} from "@fortawesome/free-regular-svg-icons";
+
config.autoAddCss = false;
library.add(
@@ -74,10 +77,12 @@ library.add(
faFileImage,
faFileImport,
faFilePdf,
+ faFilter,
faFolder,
faFolderOpen,
faFolderPlus,
faGear,
+ faGithub,
faGripVertical,
faLink,
faMinus,
@@ -89,6 +94,7 @@ library.add(
faRocket,
faSignOut,
faSlash,
+ faSquare,
faSquareCheck,
faTable,
faTrash,
@@ -99,6 +105,4 @@ library.add(
faWarning
);
-library.add(faGithub);
-
dom.watch();
diff --git a/app/assets/stylesheets/common/_filter_modal.scss b/app/assets/stylesheets/common/_filter_modal.scss
new file mode 100644
index 0000000000..1033d6a051
--- /dev/null
+++ b/app/assets/stylesheets/common/_filter_modal.scss
@@ -0,0 +1,58 @@
+@import 'constants';
+
+.filter-modal {
+ .filter-modal-title {
+ padding-left: 30px;
+ }
+
+ .filter-icon-title {
+ padding-right: 10px;
+ }
+
+ .annotation-input {
+ margin: 5px;
+
+ input[type='text'] {
+ height: 30px;
+ width: $dropdown-horizontal;
+ }
+ }
+
+ .filter {
+ margin: 5px;
+ }
+
+ .order {
+ padding-top: 10px;
+ }
+
+ .range {
+ background: $background-main;
+ max-width: $dropdown-horizontal;
+ min-height: 30px;
+
+ > span {
+ margin-left: 10px;
+ margin-right: 10px;
+ }
+
+ input[type='number'] {
+ width: 80px;
+ }
+
+ > input:valid ~ .hidden {
+ display: none;
+ }
+
+ .hidden {
+ display: inline-flex;
+ padding-top: 5px;
+ width: $dropdown-horizontal;
+
+ .validity {
+ color: $severe-error;
+ margin-left: 2px;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/common/_filter_modal_dropdown.scss b/app/assets/stylesheets/common/_filter_modal_dropdown.scss
new file mode 100644
index 0000000000..8e5672d6ac
--- /dev/null
+++ b/app/assets/stylesheets/common/_filter_modal_dropdown.scss
@@ -0,0 +1,16 @@
+@import 'constants';
+
+@mixin filter-modal-dropdown {
+ height: 30px;
+ width: $dropdown-horizontal;
+
+ ul {
+ display: block;
+ max-height: 120px;
+ overflow-y: auto;
+ }
+
+ .fa-xmark {
+ color: $primary-one;
+ }
+}
diff --git a/app/assets/stylesheets/common/_markus.scss b/app/assets/stylesheets/common/_markus.scss
index 02c858135a..2c895c7be1 100644
--- a/app/assets/stylesheets/common/_markus.scss
+++ b/app/assets/stylesheets/common/_markus.scss
@@ -15,6 +15,9 @@
@import 'courses';
@import 'url_viewer';
@import 'statistics';
+@import 'multi_select_dropdown';
+@import 'single_select_dropdown';
+@import 'filter_modal';
/** Main */
diff --git a/app/assets/stylesheets/common/_modals.scss b/app/assets/stylesheets/common/_modals.scss
index c095eba63b..93781005c7 100644
--- a/app/assets/stylesheets/common/_modals.scss
+++ b/app/assets/stylesheets/common/_modals.scss
@@ -21,6 +21,18 @@
}
}
+.modal-container-scrollable {
+ align-items: flex-start;
+ display: flex;
+ max-height: 300px;
+ overflow-y: auto;
+
+ > * {
+ margin: 5px;
+ padding: 5px;
+ }
+}
+
.modal-container-vertical {
display: flex;
flex-direction: column;
diff --git a/app/assets/stylesheets/common/_multi_select_dropdown.scss b/app/assets/stylesheets/common/_multi_select_dropdown.scss
new file mode 100644
index 0000000000..c021a0198c
--- /dev/null
+++ b/app/assets/stylesheets/common/_multi_select_dropdown.scss
@@ -0,0 +1,57 @@
+@import 'constants';
+@import 'filter_modal_dropdown';
+
+.dropdown.multi-select-dropdown {
+ @include filter-modal-dropdown;
+ padding: 0.25em;
+
+ .tags-box {
+ display: inline-block;
+ height: 22px;
+ overflow-y: auto;
+ width: $dropdown-horizontal - 50px;
+
+ .tag {
+ border: 1px solid $primary-three;
+ border-radius: $radius;
+ display: inline-block;
+ font-size: x-small;
+ margin: 1pt;
+ padding: 0.1em 0.2em;
+
+ span {
+ margin-right: 0.25em;
+ }
+ }
+ }
+
+ ul {
+ li {
+ > div {
+ display: inline-block;
+ }
+
+ > span {
+ margin-left: 2pt;
+ }
+
+ .fa-square,
+ .fa-square-check {
+ color: $primary-one;
+ }
+
+ &:hover,
+ &.active {
+ .fa-square-check,
+ .fa-square {
+ color: $background-main;
+ }
+ }
+ }
+ }
+
+ .options {
+ display: inline-flex;
+ padding: 0.25em;
+ }
+}
diff --git a/app/assets/stylesheets/common/_single_select_dropdown.scss b/app/assets/stylesheets/common/_single_select_dropdown.scss
new file mode 100644
index 0000000000..90422e23d4
--- /dev/null
+++ b/app/assets/stylesheets/common/_single_select_dropdown.scss
@@ -0,0 +1,15 @@
+@import 'constants';
+@import 'filter_modal_dropdown';
+
+.dropdown.single-select-dropdown {
+ @include filter-modal-dropdown;
+
+ a {
+ display: inline-block;
+ height: 17px;
+ overflow-x: hidden;
+ overflow-y: auto;
+ width: 140px;
+ word-wrap: break-word;
+ }
+}
diff --git a/app/controllers/results_controller.rb b/app/controllers/results_controller.rb
index 85429d01b0..44da2ac25d 100644
--- a/app/controllers/results_controller.rb
+++ b/app/controllers/results_controller.rb
@@ -21,6 +21,7 @@ def show
result = record
submission = result.submission
assignment = submission.assignment
+ course = assignment.course
remark_submitted = submission.remark_submitted?
original_result = remark_submitted ? submission.get_original_result : nil
is_review = result.is_a_review?
@@ -117,6 +118,7 @@ def show
end
if current_role.instructor? || current_role.ta?
+ data[:sections] = course.sections.pluck(:name)
data[:notes_count] = submission.grouping.notes.count
data[:num_marked] = assignment.get_num_marked(current_role.instructor? ? nil : current_role.id)
data[:num_collected] = assignment.get_num_collected(current_role.instructor? ? nil : current_role.id)
@@ -135,6 +137,10 @@ def show
data[:members] = []
end
+ if current_role.instructor?
+ data[:tas] = assignment.ta_memberships.joins(:user).distinct.pluck('users.user_name', 'users.display_name')
+ end
+
# Marks
fields = [:id, :name, :description, :position, :max_mark]
criteria_query = { assessment_id: is_review ? assignment.pr_assignment.id : assignment.id }
@@ -298,6 +304,7 @@ def remove_tag
end
def next_grouping
+ filter_data = params[:filterData]
result = record
grouping = result.submission.grouping
assignment = grouping.assignment
@@ -313,7 +320,7 @@ def next_grouping
next_result = Result.find_by(id: next_grouping&.result_id)
else
reversed = params[:direction] != '1'
- next_grouping = grouping.get_next_grouping(current_role, reversed)
+ next_grouping = grouping.get_next_grouping(current_role, reversed, filter_data)
next_result = next_grouping&.current_result
end
diff --git a/app/models/assignment.rb b/app/models/assignment.rb
index bf15b4460b..0c6e97a96d 100644
--- a/app/models/assignment.rb
+++ b/app/models/assignment.rb
@@ -879,6 +879,10 @@ def current_results
'results.created_at = sub.results_created_at')
end
+ def current_remark_results
+ self.current_results.where.not('results.remark_request_submitted_at' => nil)
+ end
+
# Query for all non-peer review results for this assignment (for the current submissions)
def non_pr_results
Result.joins(:grouping)
diff --git a/app/models/grouping.rb b/app/models/grouping.rb
index 4ba92b859c..b54272bc06 100644
--- a/app/models/grouping.rb
+++ b/app/models/grouping.rb
@@ -707,37 +707,31 @@ def access_repo
end
end
- def get_next_grouping(current_role, reversed)
+ def get_next_grouping(current_role, reversed, filter_data = nil)
if current_role.ta?
- # Get relevant groupings for a TA
- groupings = current_role.groupings
- .where(assignment: assignment)
- .joins(:group)
- .order('group_name')
+ results = self.assignment.current_results.joins(grouping: :tas).where(
+ 'roles.id': current_role.id
+ )
+
else
- # Get all groupings in an assignment -- typically for an instructor
- groupings = assignment.groupings.joins(:group).order('group_name')
+ results = self.assignment.current_results
end
- if !reversed
- # get next grouping with a result
- next_grouping = groupings.where('group_name > ?', self.group.group_name).where(is_collected: true).first
- else
- # get previous grouping with a result
- next_grouping = groupings.where('group_name < ?', self.group.group_name).where(is_collected: true).last
+ results = results.joins(grouping: :group)
+ if filter_data.nil?
+ filter_data = {}
end
- next_grouping
+ results = filter_results(current_role, results, filter_data)
+ order_and_get_next_grouping(results, filter_data, reversed)
end
def get_random_incomplete(current_role)
if current_role.ta?
- # Get relevant groupings for a TA
results = self.assignment.current_results.joins(grouping: :tas).where(
marking_state: Result::MARKING_STATES[:incomplete],
'roles.id': current_role.id
)
else
- # Get all groupings in an assignment -- typically for an instructor
results = self.assignment.current_results.where(
marking_state: Result::MARKING_STATES[:incomplete]
)
@@ -747,6 +741,138 @@ def get_random_incomplete(current_role)
private
+ # Takes in a collection of results specified by +results+, and filters them using +filter_data+. Assumes
+ # +filter_data+ is not nil.
+ # +filter_data['annotationText']+ is a string specifying some annotation text to filter by.
+ # +filter_data['section']+ is a string specifying the name of the section to filter by.
+ # +filter_data['markingState']+ is a string specifying the marking state to filter by; valid strings
+ # include "remark_requested", "released", "complete", "in_progress" and "".
+ # +filter_data['tas']+ is a list of strings corresponding to ta user names specifying the tas to filter by.
+ # +filter_data['tags']+ is a list of strings corresponding to tag names specifying the tags to filter by.
+ # +filter_data['totalMarkRange']+ is a hash with the keys 'min' and 'max' each mapping to a string representing a
+ # float. 'max' is the maximum and 'min' is the minimum total mark a result should have.
+ # +filter_data['totalExtraMarkRange']+ is a hash with the keys 'min' and 'max' each mapping to a string representing
+ # a float. 'max' is the maximum and 'min' is the minimum total extra mark a result should have.
+ # To avoid filtering by any of the specified filters, don't set values for the corresponding key in +filter_data+
+ # or set it to nil. If the value for a key is blank (false, empty, or a whitespace string, as determined by
+ # `.blank?`), no filtering will occur for the corresponding option.
+ def filter_results(current_role, results, filter_data)
+ if filter_data['annotationText'].present?
+ results = results.joins(annotations: :annotation_text)
+ .where('lower(annotation_texts.content) LIKE ?',
+ "%#{AnnotationText.sanitize_sql_like(filter_data['annotationText'].downcase)}%")
+ end
+ if filter_data['section'].present?
+ results = results.joins(grouping: :section).where('section.name': filter_data['section'])
+ end
+ if filter_data['markingState'].present?
+ remark_results = results.where.not('results.remark_request_submitted_at': nil)
+ .where('results.marking_state': Result::MARKING_STATES[:incomplete])
+ released_results = results.where.not('results.id': remark_results).where('results.released_to_students': true)
+ case filter_data['markingState']
+ when 'remark_requested'
+ results = remark_results
+ when 'released'
+ results = released_results
+ when 'complete'
+ results = results.where.not('results.id': released_results)
+ .where('results.marking_state': Result::MARKING_STATES[:complete])
+ when 'in_progress'
+ results = results.where.not('results.id': remark_results).where.not('results.id': released_results)
+ .where('results.marking_state': Result::MARKING_STATES[:incomplete])
+ end
+ end
+
+ unless current_role.ta? || filter_data['tas'].blank?
+ results = results.joins(grouping: { tas: :user }).where('user.user_name': filter_data['tas'])
+ end
+ if filter_data['tags'].present?
+ results = results.joins(grouping: :tags).where('tags.name': filter_data['tags'])
+ end
+ unless filter_data.dig('totalMarkRange', 'max').blank? && filter_data.dig('totalMarkRange', 'min').blank?
+ result_ids = results.ids
+ total_marks_hash = Result.get_total_marks(result_ids)
+ if filter_data.dig('totalMarkRange', 'max').present?
+ total_marks_hash.select! { |_, value| value <= filter_data['totalMarkRange']['max'].to_f }
+ end
+ if filter_data.dig('totalMarkRange', 'min').present?
+ total_marks_hash.select! { |_, value| value >= filter_data['totalMarkRange']['min'].to_f }
+ end
+ results = Result.where('results.id': total_marks_hash.keys)
+ end
+ unless filter_data.dig('totalExtraMarkRange', 'max').blank? && filter_data.dig('totalExtraMarkRange', 'min').blank?
+ result_ids = results.ids
+ total_marks_hash = Result.get_total_extra_marks(result_ids)
+ if filter_data.dig('totalExtraMarkRange', 'max').present?
+ total_marks_hash.select! do |_, value|
+ value <= filter_data['totalExtraMarkRange']['max'].to_f
+ end
+ end
+ if filter_data.dig('totalExtraMarkRange', 'min').present?
+ total_marks_hash.select! do |_, value|
+ value >= filter_data['totalExtraMarkRange']['min'].to_f
+ end
+ end
+ results = Result.where('results.id': total_marks_hash.keys)
+ end
+ results.joins(grouping: :group)
+ end
+
+ # Orders the results, specified as +results+ by using +filter_data+ and returns the next grouping using +reversed+.
+ # +reversed+ is a boolean value, true to return the next grouping and false to return the previous one.
+ # +filter_data['orderBy']+ specifies how the results should be ordered, with valid values being "group_name" and
+ # "submission_date". When this value is not specified (or nil), default ordering is applied.
+ # +filter_data['ascending']+ specifies whether results should be ordered in ascending or descending order. Valid
+ # options include "true" (corresponding to ascending order) or "false" (corresponding to descending order). When
+ # this value is not specified (or nil), the results are ordered in ascending order.
+ def order_and_get_next_grouping(results, filter_data, reversed)
+ asc_temp = filter_data['ascending'].nil? || filter_data['ascending'] == 'true' ? 'ASC' : 'DESC'
+ ascending = (asc_temp == 'ASC' && !reversed) || (asc_temp == 'DESC' && reversed) ? true : false
+ case filter_data['orderBy']
+ when 'submission_date'
+ next_grouping_ordered_submission_date(results, ascending)
+ else # group name/otherwise
+ next_grouping_ordered_group_name(results, ascending)
+ end
+ end
+
+ # Gets the next grouping by first ordering +results+ by group name in either ascending
+ # (+ascending+ = true) or descending (+ascending+ = false) order and then extracting the next grouping.
+ # If there is no next grouping, nil is returned.
+ def next_grouping_ordered_group_name(results, ascending)
+ results = results.group([:id, 'groups.group_name']).order('groups.group_name ASC')
+ if ascending
+ next_result = results.where('groups.group_name > ?', self.group.group_name).first
+ else
+ next_result = results.where('groups.group_name < ?', self.group.group_name).last
+ end
+ next_result&.grouping
+ end
+
+ # Gets the next grouping by first ordering +results+ by submission date and then by group name in either ascending
+ # (+ascending+ = true) or descending (+ascending+ = false) order and then extracting the next grouping.
+ # If there is no next grouping, nil is returned.
+ def next_grouping_ordered_submission_date(results, ascending)
+ results = results.joins(:submission).group([:id, 'groups.group_name', 'submissions.revision_timestamp'])
+ .order('submissions.revision_timestamp ASC',
+ 'groups.group_name ASC')
+ if ascending
+ next_result = results
+ .where('submissions.revision_timestamp > ?', self.current_submission_used.revision_timestamp)
+ .or(results.where('groups.group_name > ? AND submissions.revision_timestamp = ?',
+ self.group.group_name,
+ self.current_submission_used.revision_timestamp)).first
+
+ else
+ next_result = results
+ .where('submissions.revision_timestamp < ?', self.current_submission_used.revision_timestamp)
+ .or(results.where('groups.group_name < ? AND submissions.revision_timestamp = ?',
+ self.group.group_name,
+ self.current_submission_used.revision_timestamp)).last
+ end
+ next_result&.grouping
+ end
+
def add_assignment_folder(group_repo)
assignment_folder = self.assignment.repository_folder
diff --git a/config/locales/common/en.yml b/config/locales/common/en.yml
index 7f8e7cb8ed..b4471bf61c 100644
--- a/config/locales/common/en.yml
+++ b/config/locales/common/en.yml
@@ -5,6 +5,7 @@ en:
all: All
apply: Apply
cancel: Cancel
+ clear_all: Clear All
close: Close
continue: Continue
copy: Copy
@@ -51,6 +52,8 @@ en:
unlink_confirm: This will unlink the course and any assessments from this LMS. Continue?
unlink_courses: Unlink this course
markus: MarkUs
+ max: Max
+ min: Min
not_applicable: N/A
or: Or
other_info: Other Info
@@ -63,5 +66,6 @@ en:
select_filename: Select Filename
skip: Skip
this: This
+ to: to
working: Loading
write: Write
diff --git a/config/locales/views/results/en.yml b/config/locales/views/results/en.yml
index 9a02e189ca..59dd2dd036 100644
--- a/config/locales/views/results/en.yml
+++ b/config/locales/views/results/en.yml
@@ -29,6 +29,15 @@ en:
delete_extra_mark_confirm: Are you sure you want to remove this extra mark?
expand_all: Expand All
expand_unmarked: Expand Unmarked
+ filter_submissions: Filter Submissions
+ filters:
+ invalid_range: Invalid Range
+ no_options: No options available
+ order_by: Order By
+ ordering:
+ ascending: Ascending
+ descending: Descending
+ text_box_placeholder: Search text
fullscreen_enter: Fullscreen
fullscreen_exit: Leave fullscreen
keybinding:
diff --git a/spec/controllers/results_controller_spec.rb b/spec/controllers/results_controller_spec.rb
index 6e53d126f0..bf51bccd76 100644
--- a/spec/controllers/results_controller_spec.rb
+++ b/spec/controllers/results_controller_spec.rb
@@ -44,6 +44,639 @@ def self.test_unauthorized(route_name)
end
end
+ shared_examples 'ta and instructor #next_grouping with filters' do
+ let(:grouping1) { create :grouping_with_inviter_and_submission, is_collected: true }
+ let(:grouping2) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+ let(:grouping3) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+ let(:grouping4) { create :grouping, assignment: grouping1.assignment }
+ let(:groupings) { [grouping1, grouping2, grouping3, grouping4] }
+
+ context 'when annotation text filter is applied' do
+ let(:annotation_text) { create :annotation_text, content: 'aa_' }
+ before(:each) do
+ create :text_annotation, annotation_text: annotation_text, result: grouping1.current_result
+ create :text_annotation, annotation_text: annotation_text, result: grouping3.current_result
+ end
+
+ context 'when there are no more filtered submissions in the specified direction' do
+ it 'should return a response with next_grouping and next_result set to nil' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping3.id,
+ id: grouping3.current_result.id,
+ direction: 1, filterData: { annotationText: 'aa_' } }
+ expect(response.parsed_body['next_grouping']).to be_nil
+ expect(response.parsed_body['next_result']).to be_nil
+ end
+ end
+
+ context 'when there is another filtered result after the current one' do
+ it 'should return a response with the next filtered group' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { annotationText: 'aa_' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ expect(response.parsed_body['next_result']['id']).to eq(grouping3.current_result.id)
+ end
+
+ it 'shouldn\'t return the next non-filtered group' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { annotationText: 'aa_' } }
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping2.id)
+ expect(response.parsed_body['next_result']['id']).not_to eq(grouping2.current_result.id)
+ end
+ end
+
+ context 'when annotationText contains special characters (in the context of a like clause)' do
+ it 'should sanitize the string and return the next relevant result' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { annotationText: 'aa_' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ expect(response.parsed_body['next_result']['id']).to eq(grouping3.current_result.id)
+ end
+ end
+
+ context 'when we filter by a substring of the desired annotation text' do
+ it 'should return the next result containing the substring in one of its annotations' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { annotationText: 'a' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ expect(response.parsed_body['next_result']['id']).to eq(grouping3.current_result.id)
+ end
+ end
+ end
+
+ context 'section filter' do
+ let(:section) { create :section }
+ before(:each) do
+ groupings[0].inviter.update(section: section)
+ groupings[1].inviter.update(section: nil)
+ groupings[2].inviter.update(section: section)
+ end
+
+ context 'when a section has been picked' do
+ it 'should return the next group with a larger group name that satisfies the constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { section: 'Section 1' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ end
+
+ it 'should not return the next group that doesn\'t satisfy the constraint' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { section: 'Section 1' } }
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping2.id)
+ end
+ end
+
+ context 'when section is left blank' do
+ it 'should return the next grouping without constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { section: '' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping2.id)
+ end
+ end
+ end
+
+ context 'marking state filter' do
+ context 'when remark request is selected' do
+ let(:grouping2) do
+ result = create :incomplete_result
+ result.submission.update(submission_version_used: true)
+ result.grouping.update(assignment: grouping1.assignment)
+ result.grouping
+ end
+ let(:grouping3) do
+ remark_result = create :remark_result
+ remark_result.submission.update(submission_version_used: true)
+ remark_result.grouping.update(assignment: grouping1.assignment)
+ remark_result.grouping
+ end
+ let(:grouping4) do
+ remark_result = create :remark_result
+ remark_result.submission.update(submission_version_used: true)
+ remark_result.grouping.update(assignment: grouping1.assignment)
+ remark_result.grouping
+ end
+ before(:each) do
+ grouping3.current_result.update(marking_state: Result::MARKING_STATES[:complete])
+ end
+
+ it 'should respond with the next grouping with a remark requested and who has a marking state of incomplete' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { markingState: 'remark_requested' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping4.id)
+ end
+
+ it 'should not respond with a grouping whose current result is a remark result but is complete' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { markingState: 'remark_requested' } }
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping3.id)
+ end
+
+ it 'should not respond with a grouping whose current result is not a remark result' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { markingState: 'remark_requested' } }
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping2.id)
+ end
+ end
+
+ context 'when released is selected' do
+ let(:grouping2) do
+ result = create :incomplete_result
+ result.submission.update(submission_version_used: true)
+ result.grouping.update(assignment: grouping1.assignment)
+ result.grouping
+ end
+ let(:grouping3) do
+ remark_result = create :complete_result
+ remark_result.submission.update(submission_version_used: true)
+ remark_result.grouping.update(assignment: grouping1.assignment)
+ remark_result.grouping
+ end
+ let(:grouping4) do
+ remark_result = create :released_result
+ remark_result.submission.update(submission_version_used: true)
+ remark_result.grouping.update(assignment: grouping1.assignment)
+ remark_result.grouping
+ end
+
+ it 'should respond with the next grouping whose submission has been released' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { markingState: 'released' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping4.id)
+ end
+ end
+
+ context 'when complete is selected' do
+ let(:grouping2) do
+ result = create :released_result
+ result.submission.update(submission_version_used: true)
+ result.grouping.update(assignment: grouping1.assignment)
+ result.grouping
+ end
+ let(:grouping3) do
+ remark_result = create :remark_result, marking_state: Result::MARKING_STATES[:complete]
+ remark_result.submission.update(submission_version_used: true)
+ remark_result.grouping.update(assignment: grouping1.assignment)
+ remark_result.grouping
+ end
+
+ it 'should respond with the next grouping whose result is complete regardless of remark request status' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { markingState: 'complete' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ end
+
+ it 'should not respond with a released result regardless of the result\'s marking status' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { markingState: 'complete' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ end
+ end
+ context 'when in progress is selected' do
+ let(:grouping2) do
+ result = create :remark_result
+ result.submission.update(submission_version_used: true)
+ result.grouping.update(assignment: grouping1.assignment)
+ result.grouping
+ end
+ let(:grouping3) do
+ remark_result = create :released_result
+ remark_result.submission.update(submission_version_used: true)
+ remark_result.grouping.update(assignment: grouping1.assignment)
+ remark_result.grouping
+ end
+ let(:grouping4) do
+ remark_result = create :incomplete_result
+ remark_result.submission.update(submission_version_used: true)
+ remark_result.grouping.update(assignment: grouping1.assignment)
+ remark_result.grouping
+ end
+
+ it 'should respond with the next grouping whose result is incomplete' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { markingState: 'in_progress' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping4.id)
+ end
+
+ it 'should not respond with a released or remark result' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { markingState: 'in_progress' } }
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping2.id)
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping3.id)
+ end
+ end
+
+ context 'when markingState is left blank' do
+ let(:grouping2) do
+ result = create :incomplete_result
+ result.submission.update(submission_version_used: true)
+ result.grouping.update(assignment: grouping1.assignment)
+ result.grouping
+ end
+ let(:grouping3) do
+ remark_result = create :remark_result
+ remark_result.submission.update(submission_version_used: true)
+ remark_result.grouping.update(assignment: grouping1.assignment)
+ remark_result.grouping
+ end
+ let(:grouping4) do
+ remark_result = create :remark_result
+ remark_result.submission.update(submission_version_used: true)
+ remark_result.grouping.update(assignment: grouping1.assignment)
+ remark_result.grouping
+ end
+
+ it 'should return the next group regardless of marking state' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { markingState: '' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping2.id)
+ end
+ end
+ end
+
+ context 'when filtering by tags' do
+ let(:tag1) { create :tag, groupings: [grouping1, grouping3], name: 'tag1' }
+ let(:tag2) { create :tag, groupings: [grouping2, grouping3], name: 'tag2' }
+
+ context 'when a tag has been picked' do
+ it 'should return the next group with a larger group name that satisfies the constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { tags: [tag1.name] } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ end
+
+ it 'should not return the next group that doesn\'t satisfy the constraint' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { tags: [tag1.name] } }
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping2.id)
+ end
+ end
+
+ context 'when multiple tags have been picked' do
+ it 'should return the next group with a larger group name that has at least one of the tags' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { tags: [tag1.name, tag2.name] } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping2.id)
+ end
+ end
+
+ context 'when no tag has been picked' do
+ it 'should return the next grouping without constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { tags: [] } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping2.id)
+ end
+ end
+ end
+
+ context 'when filtering by total mark' do
+ let(:grouping4) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+ let(:assignment) { grouping1.assignment }
+ let(:criterion) { create :flexible_criterion, assignment: assignment, max_mark: 10 }
+ let!(:mark2) do
+ create :flexible_mark, criterion: criterion, result: grouping2.current_result, assignment: assignment, mark: 6
+ end
+ let!(:mark3) do
+ create :flexible_mark, criterion: criterion, result: grouping3.current_result, assignment: assignment, mark: 10
+ end
+ let!(:mark4) do
+ create :flexible_mark, criterion: criterion, result: grouping4.current_result, assignment: assignment, mark: 5
+ end
+
+ context 'when no range is provided' do
+ it 'should return the next grouping without constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalMarkRange: {} } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping2.id)
+ end
+ end
+
+ context 'when minimum value is provided' do
+ it 'should return the next group with a larger group name that satisfies the constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalMarkRange: { min: 7.00 } } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ end
+
+ it 'should not return the next group that doesn\'t satisfy the constraint' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalMarkRange: { min: 7.00 } } }
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping2.id)
+ end
+ end
+
+ context 'when maximum value is provided' do
+ it 'should return the next group with a larger group name that satisfies the constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalMarkRange: { max: 5.00 } } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping4.id)
+ end
+
+ it 'should not return the next group that doesn\'t satisfy the constraint' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalMarkRange: { max: 5.00 } } }
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping2.id)
+ end
+ end
+
+ context 'when minimum and maximum values are provided' do
+ it 'should return the next group with a larger group name that satisfies the constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalMarkRange: { min: 4.00, max: 5.00 } } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping4.id)
+ end
+
+ it 'should not return the next group that doesn\'t satisfy the constraint' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalMarkRange: { min: 4.00, max: 5.00 } } }
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping2.id)
+ end
+ end
+ end
+
+ context 'when filtering by total extra mark' do
+ let(:grouping4) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+ let(:assignment) { grouping1.assignment }
+ let!(:mark2) { create :extra_mark_points, result: grouping2.current_result, extra_mark: 6 }
+ let!(:mark3) { create :extra_mark_points, result: grouping3.current_result, extra_mark: 10 }
+ let!(:mark4) { create :extra_mark_points, result: grouping4.current_result, extra_mark: 5 }
+
+ context 'when no range is provided' do
+ it 'should return the next grouping without constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalExtraMarkRange: {} } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping2.id)
+ end
+ end
+
+ context 'when minimum value is provided' do
+ it 'should return the next group with a larger group name that satisfies the constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalExtraMarkRange: { min: 7.00 } } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ end
+
+ it 'should not return the next group that doesn\'t satisfy the constraint' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalExtraMarkRange: { min: 7.00 } } }
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping2.id)
+ end
+ end
+
+ context 'when maximum value is provided' do
+ it 'should return the next group with a larger group name that satisfies the constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalExtraMarkRange: { max: 5.00 } } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping4.id)
+ end
+
+ it 'should not return the next group that doesn\'t satisfy the constraint' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalExtraMarkRange: { max: 5.00 } } }
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping2.id)
+ end
+ end
+
+ context 'when minimum and maximum values are provided' do
+ it 'should return the next group with a larger group name that satisfies the constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalExtraMarkRange: { min: 4.00, max: 5.00 } } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping4.id)
+ end
+
+ it 'should not return the next group that doesn\'t satisfy the constraint' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { totalExtraMarkRange: { min: 4.00, max: 5.00 } } }
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping2.id)
+ end
+ end
+ end
+ end
+
+ shared_examples 'instructor and ta #next_grouping with different orderings' do
+ context 'with 3 groupings' do
+ let(:grouping1) { create :grouping_with_inviter_and_submission, is_collected: true }
+ let(:grouping2) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+ let(:grouping3) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+ let(:groupings) { [grouping1, grouping2, grouping3] }
+
+ context 'order by group name' do
+ context 'Descending Order' do
+ context 'direction = 1' do
+ it 'should return the next grouping in descending order of group name' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping2.id,
+ id: grouping2.current_result.id,
+ direction: 1, filterData: { ascending: 'false', orderBy: 'group_name' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping1.id)
+ end
+ end
+
+ context 'direction = -1' do
+ it 'should return the previous grouping in descending order of group name' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping2.id,
+ id: grouping2.current_result.id,
+ direction: -1, filterData: { ascending: 'false', orderBy: 'group_name' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ end
+ end
+ end
+ end
+
+ context 'order by submission date' do
+ context 'Ascending Order' do
+ context 'when direction = 1' do
+ context 'when the ordered submission has a different submission date from the current one' do
+ it 'should return the grouping with the next latest submission date' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping2.id,
+ id: grouping2.current_result.id,
+ direction: 1, filterData:
+ { ascending: 'true', orderBy: 'submission_date' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ end
+ end
+
+ context 'when the next ordered submission shares has the same submission date as the current one' do
+ let(:grouping1) { create :grouping_with_inviter_and_submission, is_collected: true }
+ let(:grouping2) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+ let(:grouping3) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+
+ before(:each) do
+ 3.times do |i|
+ groupings[i].current_submission_used.update(revision_timestamp: Date.current)
+ end
+ end
+
+ it 'should return the grouping with the next largest group name with the same submission date' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping2.id,
+ id: grouping2.current_result.id,
+ direction: 1, filterData:
+ { ascending: 'true', orderBy: 'submission_date' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ end
+ end
+ end
+
+ context 'direction = -1' do
+ context 'when the previous ordered submission has a different submission date from the current one' do
+ it 'should return the grouping with the next earliest submission date' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping2.id,
+ id: grouping2.current_result.id,
+ direction: -1, filterData:
+ { ascending: 'true', orderBy: 'submission_date' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping1.id)
+ end
+ end
+
+ context 'when the previous ordered submission shares has the same submission date as the current one' do
+ let(:grouping1) { create :grouping_with_inviter_and_submission, is_collected: true }
+ let(:grouping2) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+ let(:grouping3) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+ before(:each) do
+ 3.times do |i|
+ groupings[i].current_submission_used.update(revision_timestamp: Date.current)
+ end
+ end
+
+ it 'should return the grouping with the next smallest group name with the same submission date' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping2.id,
+ id: grouping2.current_result.id,
+ direction: -1, filterData:
+ { ascending: 'true', orderBy: 'submission_date' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping1.id)
+ end
+ end
+ end
+ end
+
+ context 'Descending Order' do
+ context 'direction = 1' do
+ context 'when the next ordered submission has a different submission date from the current one' do
+ it 'should return the grouping with the next earliest submission date' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping2.id,
+ id: grouping2.current_result.id,
+ direction: 1, filterData:
+ { ascending: 'false', orderBy: 'submission_date' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping1.id)
+ end
+ end
+
+ context 'when the next ordered submission shares has the same submission date as the current one' do
+ let(:grouping1) { create :grouping_with_inviter_and_submission, is_collected: true }
+ let(:grouping2) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+ let(:grouping3) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+ before(:each) do
+ 3.times do |i|
+ groupings[i].current_submission_used.update(revision_timestamp: Date.current)
+ end
+ end
+
+ it 'should return the grouping with the next smallest group name with the same submission date' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping2.id,
+ id: grouping2.current_result.id,
+ direction: 1, filterData:
+ { ascending: 'false', orderBy: 'submission_date' } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping1.id)
+ end
+ end
+ end
+
+ context 'direction = -1' do
+ context 'when the previous ordered submission has a different submission date from the current one' do
+ it 'should return the grouping with the next latest submission date' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping2.id,
+ id: grouping2.current_result.id,
+ direction: -1, filterData: {
+ ascending: 'false', orderBy: 'submission_date'
+ } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ end
+ end
+
+ context 'when the previous ordered submission shares has the same submission date as the current one' do
+ let(:grouping1) { create :grouping_with_inviter_and_submission, is_collected: true }
+ let(:grouping2) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+ let(:grouping3) do
+ create :grouping_with_inviter_and_submission, assignment: grouping1.assignment, is_collected: true
+ end
+ before(:each) do
+ 3.times do |i|
+ groupings[i].current_submission_used.update(revision_timestamp: Date.current)
+ end
+ end
+
+ it 'should return the grouping with the next largest group name with the same submission date' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping2.id,
+ id: grouping2.current_result.id,
+ direction: -1, filterData: {
+ ascending: 'false', orderBy: 'submission_date'
+ } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+
shared_examples 'shared ta and instructor tests' do
context 'accessing next_grouping' do
it 'should receive 200 when current grouping has a submission' do
@@ -934,6 +1567,60 @@ def self.test_unauthorized(route_name)
end
end
+ context 'accessing next_grouping' do
+ let(:grouping1) { create :grouping_with_inviter_and_submission }
+ let(:grouping2) { create :grouping_with_inviter_and_submission, assignment: grouping1.assignment }
+ let(:grouping3) { create :grouping_with_inviter_and_submission, assignment: grouping1.assignment }
+ let(:grouping4) { create :grouping_with_inviter_and_submission, assignment: grouping1.assignment }
+ let(:groupings) { [grouping1, grouping2, grouping3, grouping4] }
+ before(:each) { groupings }
+ include_examples 'ta and instructor #next_grouping with filters'
+ include_examples 'instructor and ta #next_grouping with different orderings'
+
+ context 'filter by tas' do
+ let(:ta1) { create :ta }
+ let(:ta2) { create :ta }
+ let!(:ta_membership1) { create :ta_membership, role: ta1, grouping: grouping1 }
+ let!(:ta_membership2) { create :ta_membership, role: ta1, grouping: grouping3 }
+ let!(:ta_membership3) { create :ta_membership, role: ta2, grouping: grouping3 }
+ let!(:ta_membership4) { create :ta_membership, role: ta2, grouping: grouping2 }
+
+ context 'when a ta has been picked' do
+ it 'should return the next group with a larger group name that satisfies the constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { tas: [ta1.user.user_name] } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping3.id)
+ end
+
+ it 'should not return the next group that doesn\'t satisfy the constraint' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { tas: [ta1.user.user_name] } }
+ expect(response.parsed_body['next_grouping']['id']).not_to eq(grouping2.id)
+ end
+ end
+
+ context 'when multiple tas have been picked' do
+ it 'should return the next group with a larger group name that has atleast one of the tas' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { tas: [ta1.user.user_name, ta2.user.user_name] } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping2.id)
+ end
+ end
+
+ context 'when no Ta is picked' do
+ it 'should return the next grouping without constraints' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { tas: [] } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping2.id)
+ end
+ end
+ end
+ end
+
describe '#random_incomplete_submission' do
it 'should receive 200 when current grouping has a submission' do
allow_any_instance_of(Grouping).to receive(:has_submission).and_return true
@@ -967,6 +1654,7 @@ def self.test_unauthorized(route_name)
end
end
end
+
context 'A TA' do
before(:each) { sign_in ta }
[:set_released_to_students].each { |route_name| test_unauthorized(route_name) }
@@ -1229,5 +1917,42 @@ def self.test_unauthorized(route_name)
test_unauthorized(:get_test_runs_instructors)
end
end
+ context 'accessing next_grouping with valid permissions' do
+ let(:grouping1) { create :grouping_with_inviter_and_submission }
+ let(:grouping2) { create :grouping_with_inviter_and_submission, assignment: grouping1.assignment }
+ let(:grouping3) { create :grouping_with_inviter_and_submission, assignment: grouping1.assignment }
+ let(:grouping4) { create :grouping_with_inviter_and_submission, assignment: grouping1.assignment }
+ let(:groupings) { [grouping1, grouping2, grouping3, grouping4] }
+ before(:each) do
+ 3.times do |i|
+ create :ta_membership, role: ta, grouping: groupings[i]
+ end
+ end
+ context 'ta and instructor #next_grouping with filters' do
+ before(:each) do
+ create :ta_membership, role: ta, grouping: groupings[3]
+ end
+ include_examples 'ta and instructor #next_grouping with filters'
+ end
+
+ include_examples 'instructor and ta #next_grouping with different orderings'
+ context 'filter by tas' do
+ let(:ta1) { create :ta }
+ let(:ta2) { create :ta }
+ let!(:ta_membership1) { create :ta_membership, role: ta1, grouping: grouping1 }
+ let!(:ta_membership2) { create :ta_membership, role: ta1, grouping: grouping3 }
+ let!(:ta_membership3) { create :ta_membership, role: ta2, grouping: grouping3 }
+ let!(:ta_membership4) { create :ta_membership, role: ta2, grouping: grouping2 }
+
+ context 'when a ta has been picked' do
+ it 'should return the next group with a larger group name and NOT filter by selected ta' do
+ get :next_grouping, params: { course_id: course.id, grouping_id: grouping1.id,
+ id: grouping1.current_result.id,
+ direction: 1, filterData: { tas: [ta1.user.user_name] } }
+ expect(response.parsed_body['next_grouping']['id']).to eq(grouping2.id)
+ end
+ end
+ end
+ end
end
end
diff --git a/spec/factories/submissions.rb b/spec/factories/submissions.rb
index 54e7aafcfa..85d5320ac4 100644
--- a/spec/factories/submissions.rb
+++ b/spec/factories/submissions.rb
@@ -3,7 +3,7 @@
association :grouping
submission_version { 1 }
revision_identifier { 1 }
- revision_timestamp { Date.current }
+ revision_timestamp { Time.current }
is_empty { false }
factory :version_used_submission do
diff --git a/spec/models/grouping_spec.rb b/spec/models/grouping_spec.rb
index 9b1402bb67..a3c1c707f7 100644
--- a/spec/models/grouping_spec.rb
+++ b/spec/models/grouping_spec.rb
@@ -1534,8 +1534,8 @@ def expect_updated_criteria_coverage_count_eq(expected_count)
describe '#get_next_group as instructor' do
let(:role) { create :instructor }
let(:assignment) { create :assignment }
- let!(:grouping1) { create :grouping, assignment: assignment, is_collected: true }
- let!(:grouping2) { create :grouping, assignment: assignment, is_collected: true }
+ let!(:grouping1) { create :grouping_with_inviter_and_submission, assignment: assignment }
+ let!(:grouping2) { create :grouping_with_inviter_and_submission, assignment: assignment }
it 'should let one navigate right if there is a result directly to the right' do
groupings = assignment.groupings.joins(:group).order('group_name')
new_grouping = groupings.first.get_next_grouping(role, false)
@@ -1558,7 +1558,7 @@ def expect_updated_criteria_coverage_count_eq(expected_count)
end
describe 'with collected results separated by an uncollected results' do
let!(:grouping2) { create :grouping, assignment: assignment, is_collected: false }
- let!(:grouping3) { create :grouping, assignment: assignment, is_collected: true }
+ let!(:grouping3) { create :grouping_with_inviter_and_submission, assignment: assignment, is_collected: true }
it 'should let me navigate to the right if any result exists towards the right' do
groupings = assignment.groupings.joins(:group).order('group_name')
new_grouping = groupings.first.get_next_grouping(role, false)
@@ -1573,9 +1573,15 @@ def expect_updated_criteria_coverage_count_eq(expected_count)
end
describe '#get_next_group as ta' do
let(:assignment) { create :assignment }
- let(:role) { create :ta, groupings: assignment.groupings }
- let!(:grouping1) { create :grouping, assignment: assignment, is_collected: true }
- let!(:grouping2) { create :grouping, assignment: assignment, is_collected: true }
+ let(:role) { create :ta }
+ let!(:grouping1) { create :grouping_with_inviter_and_submission, assignment: assignment }
+ let!(:grouping2) { create :grouping_with_inviter_and_submission, assignment: assignment }
+ let(:groupings) { [grouping1, grouping2] }
+ before(:each) do
+ 2.times do |i|
+ create :ta_membership, role: role, grouping: groupings[i]
+ end
+ end
it 'should let one navigate right if there is a result directly to the right' do
groupings = assignment.groupings.joins(:group).order('group_name')
new_grouping = groupings.first.get_next_grouping(role, false)
@@ -1598,7 +1604,10 @@ def expect_updated_criteria_coverage_count_eq(expected_count)
end
describe 'with collected results separated by an uncollected results' do
let!(:grouping2) { create :grouping, assignment: assignment, is_collected: false }
- let!(:grouping3) { create :grouping, assignment: assignment, is_collected: true }
+ let!(:grouping3) { create :grouping_with_inviter_and_submission, assignment: assignment }
+ before(:each) do
+ create :ta_membership, role: role, grouping: grouping3
+ end
it 'should let me navigate to the right if any result exists towards the right' do
groupings = assignment.groupings.joins(:group).order('group_name')
new_grouping = groupings.first.get_next_grouping(role, false)