Skip to content

Conversation

@lxyhan
Copy link
Contributor

@lxyhan lxyhan commented Oct 10, 2025

Proposed Changes

Added datetime-based visibility scheduling for assessments. Instructors can now set visible_on and visible_until dates to automatically show/hide assessments instead of manually toggling them.

Right now instructors can only mark assessments as visible or hidden with a boolean. They've been requesting the ability to schedule when assessments become visible (like "October 1 - December 31") so they can set it up at the start of the semester and not worry about manually publishing assignments later.

My changes in this PR

Database:

  • Added visible_on and visible_until datetime columns to assessments and assessment_section_properties tables
  • Updated the check_repo_permissions SQL function to enforce datetime visibility for git access

Models:

  • Updated Student#visible_assessments to check datetime ranges
  • Added validations to ensure visible_on < visible_until
  • Datetime columns override is_hidden when they're set

API:

  • Exposed visible_on and visible_until fields in assignments and grade entry forms API endpoints

Tests:

  • Added validation tests for datetime columns

  • Added 18 test cases for visibility logic (global, section-specific, datetime ranges, edge cases)

  • Added 9 policy tests for datetime visibility authorization

  • If visible_on/visible_until are set, they override the is_hidden boolean

  • Section-specific datetime settings override global settings (same pattern as section due dates)

  • Backwards compatible - if datetime columns are null, falls back to is_hidden

  • Can set just visible_on, just visible_until, or both

Type of Change

(Write an X or a brief description next to the type or types that best describe your changes.)

Type Applies?
🚨 Breaking change (fix or feature that would cause existing functionality to change)
New feature (non-breaking change that adds functionality) x
🐛 Bug fix (non-breaking change that fixes an issue)
🎨 User interface change (change to user interface; provide screenshots)
♻️ Refactoring (internal change to codebase, without changing functionality)
🚦 Test update (change that only adds or modifies tests)
📦 Dependency update (change that updates a dependency)
🔧 Internal (change that only affects developers or continuous integration)

Checklist

(Complete each of the following items for your pull request. Indicate that you have completed an item by changing the [ ] into a [x] in the raw text, or by clicking on the checkbox in the rendered description on GitHub.)

Before opening your pull request:

  • I have performed a self-review of my changes.
    • Check that all changed files included in this pull request are intentional changes.
    • Check that all changes are relevant to the purpose of this pull request, as described above.
  • I have added tests for my changes, if applicable.
    • This is required for all bug fixes and new features.
  • I have updated the project documentation, if applicable.
    • This is required for new features.
  • If this is my first contribution, I have added myself to the list of contributors.

After opening your pull request:

  • I have updated the project Changelog (this is required for all changes).
  • I have verified that the pre-commit.ci checks have passed.
  • I have verified that the CI tests have passed.
  • I have reviewed the test coverage changes reported by Coveralls.
  • I have requested a review from a project maintainer.

Questions and Comments

(Include any questions or comments you have regarding your changes.)

Add visible_on/visible_until columns to schedule assessment visibility
automatically. Datetime columns override is_hidden when set.
Section-specific datetime overrides global datetime. Updated models,
policies, SQL function, API controllers, and added comprehensive tests.
@lxyhan lxyhan changed the title Add scheduled assessment visibility Add scheduled visibility for assessments Oct 10, 2025
@lxyhan lxyhan marked this pull request as ready for review October 11, 2025 20:16
@lxyhan lxyhan requested a review from david-yz-liu October 11, 2025 20:18
@coveralls
Copy link
Collaborator

coveralls commented Oct 13, 2025

Pull Request Test Coverage Report for Build 18783844054

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 193 of 198 (97.47%) changed or added relevant lines in 10 files are covered.
  • 55 unchanged lines in 4 files lost coverage.
  • Overall coverage increased (+0.06%) to 91.574%

Changes Missing Coverage Covered Lines Changed/Added Lines %
app/models/assessment_section_properties.rb 6 7 85.71%
app/lib/repository.rb 13 17 76.47%
Files with Coverage Reduction New Missed Lines %
app/models/result.rb 2 99.17%
app/javascript/Components/Result/marks_panel.jsx 17 43.02%
app/models/assignment.rb 18 93.82%
app/javascript/Components/graders_manager.jsx 18 41.64%
Totals Coverage Status
Change from base Build 18414815512: 0.06%
Covered Lines: 42751
Relevant Lines: 45906

💛 - Coveralls

Copy link
Collaborator

@david-yz-liu david-yz-liu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lxyhan nice work! I left a few inline comments. Note that my comments on the queries apply to both branches of the if statement.

In addition to the inline comments:

  1. Add tests for the SQL function in spec/db/check_repo_permissions_spec.rb
  2. You also need to update Repository.visibility_hash and Assignment#visibility_changed?

Changelog.md Outdated
### 🚨 Breaking changes

### ✨ New features and improvements
- Added datetime-based visibility scheduling for assessments with `visible_on` and `visible_until` columns
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure to include the PR number here


def visible_dates_are_valid
return if visible_on.nil? || visible_until.nil?
return unless visible_on.respond_to?(:>=) && visible_until.respond_to?(:>=)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this line is necessary, if it's reach then shouldn't these attributes be dates?


has_one :course, through: :assessment
validate :courses_should_match
validate :visible_dates_are_valid
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validations are typically run in top-down order, so it would be more typical to write this validation below the next two (so that the types are checked first)

)

# Use datetime if set, otherwise fall back to is_hidden
visible.where(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified using built-in ActiveRecord methods. In this case you can use both .where.not(...) and .or

visible = visible.where(is_hidden: false)
# No section assigned - just check global visibility
visible = visible.where(
'(assessments.visible_on IS NULL OR assessments.visible_on <= ?) AND ' \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similar to my comments below, I'd prefer using the ActiveRecord methods where possible. In this case the part with the <= is fine, but you can use .or instead of SQL OR and chained .wheres instead of AND

@lxyhan lxyhan requested a review from david-yz-liu October 15, 2025 22:08
section_visible = section_visible
.where.not(assessment_section_properties: { visible_on: nil })
.or(section_visible.where.not(assessment_section_properties: { visible_until: nil }))
.or(section_visible.where(assessment_section_properties: { is_hidden: false }))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is something subtle here with the is_hidden section property.

In the case that for the section properties, visible_on: nil, visible_until: nil, is_hidden: true, then the assignment should not be visible to students in that section, regardless of what the global properties are. I don't think this is currently being implemented correctly by this logic.

You handled this differently in the visibility_hash method by first checking whether any of the three section properties were non-nil before overriding the global properties.

And in the SQL function, you're only checking assessment_section_properties.is_hidden IS NOT NULL but should probably be checking all three.

expect(student.visible_assessments).not_to include(assignment)
end

it 'shows assessment when datetime range is valid (overrides is_hidden=true)' do
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The word "valid" is not useful in this context. Better wording would be "when the datetime range includes the current time". This repeats a few times in the test descriptions.

@lxyhan lxyhan requested a review from david-yz-liu October 18, 2025 12:36
Copy link
Collaborator

@david-yz-liu david-yz-liu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lxyhan nice work, I just left two small comments


# Check section-specific visibility first (takes precedence when any section property is set)
section_visible = visible.where('assessment_section_properties.section_id': self.section_id)
.where('assessment_section_properties.is_hidden IS NOT NULL OR ' \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a place where you can use ActiveRecord methods like .or and .not


# Check global visibility (when no section-specific settings exist)
global_visible = visible.where('assessment_section_properties.section_id': nil)
.or(visible.where('assessment_section_properties.is_hidden IS NULL AND ' \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a similar comment here, you can both use : nil and pass multiple conditions to .where instead of relying on raw SQL

@lxyhan lxyhan requested a review from david-yz-liu October 19, 2025 12:55
Copy link
Collaborator

@david-yz-liu david-yz-liu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh @lxyhan I just realized that in 322c3a8 you modified structure.sql directly, but did not modify the migration file. You should always modify migrations rather than structure.sql.

In this case, you can first rollback the existing migration, and then modify it, and then run it again. You won't see any changes in git to the structure.sql file, but you will see the changes to your migration file, which should be committed and pushed.

@lxyhan lxyhan requested a review from david-yz-liu October 24, 2025 16:05
Copy link
Collaborator

@david-yz-liu david-yz-liu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work, @lxyhan!

@david-yz-liu david-yz-liu merged commit 2ca6a7b into MarkUsProject:master Oct 24, 2025
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants