From 184e4c2b2b4480202ded95456bdf0aa6e36ee15a Mon Sep 17 00:00:00 2001
From: Emily Xie <30580023+emiilyxie@users.noreply.github.com>
Date: Sat, 26 Apr 2025 18:48:08 -0400
Subject: [PATCH 1/7] add basic structure for creating rubrics for problems
---
app/assets/javascripts/rubric_items.js | 114 ++++++++++++++++++
app/controllers/rubric_items_controller.rb | 68 +++++++++++
app/models/problem.rb | 1 +
app/models/rubric_item.rb | 10 ++
app/views/problems/_fields.html.erb | 71 ++++++++++-
app/views/rubric_items/edit.html.erb | 22 ++++
app/views/rubric_items/new.html.erb | 17 +++
config/routes.rb | 5 +-
.../20250426203028_create_rubric_items.rb | 13 ++
db/schema.rb | 111 ++++++++++-------
10 files changed, 383 insertions(+), 49 deletions(-)
create mode 100644 app/assets/javascripts/rubric_items.js
create mode 100644 app/controllers/rubric_items_controller.rb
create mode 100644 app/models/rubric_item.rb
create mode 100644 app/views/rubric_items/edit.html.erb
create mode 100644 app/views/rubric_items/new.html.erb
create mode 100644 db/migrate/20250426203028_create_rubric_items.rb
diff --git a/app/assets/javascripts/rubric_items.js b/app/assets/javascripts/rubric_items.js
new file mode 100644
index 000000000..c4a5e9e13
--- /dev/null
+++ b/app/assets/javascripts/rubric_items.js
@@ -0,0 +1,114 @@
+$(document).ready(function() {
+ // Add new rubric item
+ $('#add-rubric-item').click(function(e) {
+ e.preventDefault();
+ var newItem = createRubricItem();
+ $('#rubric-items').append(newItem);
+ M.updateTextFields();
+ });
+
+ // Delete rubric item
+ $(document).on('click', '.delete-rubric-item', function(e) {
+ e.preventDefault();
+ var item = $(this).closest('.rubric-item');
+ var id = item.data('id');
+
+ if (id) {
+ // If item exists in database, send delete request
+ $.ajax({
+ url: deletePath(id),
+ method: 'DELETE',
+ headers: {
+ 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
+ },
+ success: function() {
+ item.remove();
+ M.toast({html: 'Rubric item deleted'});
+ },
+ error: function() {
+ M.toast({html: 'Error deleting rubric item'});
+ }
+ });
+ } else {
+ // If item is new (no id), just remove from DOM
+ item.remove();
+ }
+ });
+
+ // Save rubric item changes
+ $(document).on('change', '.rubric-description, .rubric-points', function() {
+ var item = $(this).closest('.rubric-item');
+ var id = item.data('id');
+ var data = {
+ description: item.find('.rubric-description').val(),
+ points: item.find('.rubric-points').val(),
+ order: item.index()
+ };
+ console.log("data", data, id, basePath, item);
+ if (!data.description || !data.points) {
+ M.toast({html: 'Description and points are required'});
+ return;
+ }
+
+ if (id) {
+ // Update existing item
+ $.ajax({
+ url: updatePath(id),
+ method: 'PATCH',
+ data: data,
+ headers: {
+ 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
+ },
+ success: function() {
+ M.toast({html: 'Rubric item saved'});
+ },
+ error: function() {
+ M.toast({html: 'Error saving rubric item'});
+ }
+ });
+ } else {
+ // Create new item
+ data.problem_id = window.location.pathname.split('/')[4];
+ $.ajax({
+ url: createPath,
+ method: 'POST',
+ data: data,
+ headers: {
+ 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
+ },
+ success: function(response) {
+ item.data('id', response.id);
+ M.toast({html: 'Rubric item created'});
+ },
+ error: function() {
+ M.toast({html: 'Error creating rubric item'});
+ }
+ });
+ }
+ });
+
+ // Path helpers for AJAX requests
+ var createPath = basePath + "/rubric_items";
+ var updatePath = function(id) {
+ return basePath + "/rubric_items/" + id;
+ };
+ var deletePath = updatePath;
+});
+
+function createRubricItem() {
+ return `
+
+
+
+
+
+
+
+
+
+ delete
+
+
+
+ `;
+}
\ No newline at end of file
diff --git a/app/controllers/rubric_items_controller.rb b/app/controllers/rubric_items_controller.rb
new file mode 100644
index 000000000..e66a0b7fc
--- /dev/null
+++ b/app/controllers/rubric_items_controller.rb
@@ -0,0 +1,68 @@
+class RubricItemsController < ApplicationController
+ before_action :set_assessment
+ before_action :set_problem
+ before_action :set_rubric_item, only: [:edit, :update, :destroy]
+
+ action_auth_level :new, :instructor
+ def new
+ @rubric_item = @problem.rubric_items.new
+ end
+
+ action_auth_level :create, :instructor
+ def create
+ @rubric_item = @problem.rubric_items.new(rubric_item_params)
+ @rubric_item.order = @problem.rubric_items.count
+
+ if @rubric_item.save
+ flash[:success] = "Rubric item created successfully"
+ redirect_to edit_course_assessment_problem_path(@course, @assessment, @problem)
+ else
+ flash[:error] = "Error creating rubric item"
+ @rubric_item.errors.full_messages.each do |msg|
+ flash[:error] += " #{msg}"
+ end
+ flash[:html_safe] = true
+ render :new
+ end
+ end
+
+ action_auth_level :edit, :instructor
+ def edit
+ end
+
+ action_auth_level :update, :instructor
+ def update
+ if @rubric_item.update(rubric_item_params)
+ flash[:success] = "Rubric item updated successfully"
+ redirect_to edit_course_assessment_problem_path(@course, @assessment, @problem)
+ else
+ flash[:error] = "Error updating rubric item"
+ @rubric_item.errors.full_messages.each do |msg|
+ flash[:error] += " #{msg}"
+ end
+ flash[:html_safe] = true
+ render :edit
+ end
+ end
+
+ action_auth_level :destroy, :instructor
+ def destroy
+ @rubric_item.destroy
+ flash[:success] = "Rubric item deleted successfully"
+ redirect_to edit_course_assessment_problem_path(@course, @assessment, @problem)
+ end
+
+ private
+
+ def set_problem
+ @problem = @assessment.problems.find(params[:problem_id])
+ end
+
+ def set_rubric_item
+ @rubric_item = @problem.rubric_items.find(params[:id])
+ end
+
+ def rubric_item_params
+ params.require(:rubric_item).permit(:description, :points, :order)
+ end
+end
\ No newline at end of file
diff --git a/app/models/problem.rb b/app/models/problem.rb
index bccdb1b3b..23e93d443 100755
--- a/app/models/problem.rb
+++ b/app/models/problem.rb
@@ -9,6 +9,7 @@ class Problem < ApplicationRecord
has_many :scores, dependent: :delete_all
belongs_to :assessment, touch: true
has_many :annotations, dependent: :destroy
+ has_many :rubric_items, dependent: :destroy
validates :name, :max_score, presence: true
validates :name, uniqueness: { case_sensitive: false, scope: :assessment_id }
diff --git a/app/models/rubric_item.rb b/app/models/rubric_item.rb
new file mode 100644
index 000000000..1a8951262
--- /dev/null
+++ b/app/models/rubric_item.rb
@@ -0,0 +1,10 @@
+class RubricItem < ApplicationRecord
+ belongs_to :problem
+
+ validates :description, :points, :order, presence: true
+ validates :points, numericality: { greater_than_or_equal_to: 0 }
+ validates :order, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
+ validates :order, uniqueness: { scope: :problem_id }
+
+ default_scope { order(order: :asc) }
+end
\ No newline at end of file
diff --git a/app/views/problems/_fields.html.erb b/app/views/problems/_fields.html.erb
index 2d65455cf..f478b47cd 100755
--- a/app/views/problems/_fields.html.erb
+++ b/app/views/problems/_fields.html.erb
@@ -1,3 +1,10 @@
+<% content_for :javascripts do %>
+
+ <%= javascript_include_tag "rubric_items" %>
+<% end %>
+
<%= f.text_field :name, help_text: "Each problem has a name that is unique to the assessment.",
placeholder: "problem1", required: true %>
@@ -21,6 +28,67 @@ unchecked." %>
<%= f.check_box :starred, help_text: "By default, all problems are
\"not starred\". Starred problems are displayed first in the problems dropdown when creating annotations." %>
+<% if false %>
+<% if @problem %>
+
+
Rubric Items
+
+ <% @problem.rubric_items.each do |item| %>
+
+
+
+
+
+
+
+
+
+ delete
+
+
+
+ <% end %>
+
+
Add Rubric Item
+
+<% end %>
+<% end %>
+
+<% if @problem %>
+
+
Rubric Items
+
+
+
+ Description
+ Points
+ Actions
+
+
+
+ <% @problem.rubric_items.each do |item| %>
+
+ <%= item.description %>
+ <%= item.points %>
+
+ <%= link_to "mode_edit ".html_safe,
+ edit_course_assessment_problem_rubric_item_path(@course, @assessment, @problem, item),
+ { class: "small" } %>
+ <%= link_to "delete ".html_safe,
+ [@course, @assessment, @problem, item],
+ method: :delete,
+ class: "small",
+ data: { confirm: "Are you sure you want to delete this rubric item?" } %>
+
+
+ <% end %>
+
+
+
+ <%= link_to "Add Rubric Item", new_course_assessment_problem_rubric_item_path(@course, @assessment, @problem), { class: "btn" } %>
+
+<% end %>
+
<%= f.submit "Save Problem", { class: "btn primary" } %>
<% if @problem %>
@@ -29,5 +97,4 @@ unchecked." %>
method: :delete, class: 'btn btn-danger',
data: { confirm: "Deleting will delete all associated problem data (such as scores and annotations) and cannot be undone. Are you sure you want to delete this problem?" } %>
- <% end %>
-
+ <% end %>
\ No newline at end of file
diff --git a/app/views/rubric_items/edit.html.erb b/app/views/rubric_items/edit.html.erb
new file mode 100644
index 000000000..231993568
--- /dev/null
+++ b/app/views/rubric_items/edit.html.erb
@@ -0,0 +1,22 @@
+<%# For navigation breadcrumbs %>
+<% @title = "Edit Rubric Item" %>
+
+<% content_for :stylesheets do %>
+ <%= stylesheet_link_tag "problems" %>
+<% end %>
+
+Edit Rubric Item
+<%= form_for [@course, @assessment, @problem, @rubric_item], builder: FormBuilderWithDateTimeInput do |f| %>
+ <%= f.text_field :description, help_text: "Description of the rubric item.", placeholder: "Description", required: true %>
+
+ <%= f.number_field :points, help_text: "Points for this rubric item.", placeholder: "0", step: "any", required: true %>
+
+
+ <%= f.submit "Save Rubric Item", { class: "btn primary" } %>
+
+ <%= link_to "Delete this Rubric Item", [@course, @assessment, @problem, @rubric_item],
+ method: :delete, class: 'btn btn-danger',
+ data: { confirm: "Are you sure you want to delete this rubric item?" } %>
+
+
+<% end %>
\ No newline at end of file
diff --git a/app/views/rubric_items/new.html.erb b/app/views/rubric_items/new.html.erb
new file mode 100644
index 000000000..fbef3f6db
--- /dev/null
+++ b/app/views/rubric_items/new.html.erb
@@ -0,0 +1,17 @@
+<%# For navigation breadcrumbs %>
+<% @title = "New Rubric Item" %>
+
+<% content_for :stylesheets do %>
+ <%= stylesheet_link_tag "problems" %>
+<% end %>
+
+Create New Rubric Item
+<%= form_for [@course, @assessment, @problem, @rubric_item], builder: FormBuilderWithDateTimeInput do |f| %>
+ <%= f.text_field :description, help_text: "Description of the rubric item.", placeholder: "Description", required: true %>
+
+ <%= f.number_field :points, help_text: "Points for this rubric item.", placeholder: "0", step: "any", required: true %>
+
+
+ <%= f.submit "Save Rubric Item", { class: "btn primary" } %>
+
+<% end %>
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index d113b57ff..c41b94dad 100755
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -155,7 +155,10 @@
post "import", on: :collection
end
- resources :problems, except: [:index, :show]
+ # resources :problems, except: [:index, :show]
+ resources :problems, except: [:index, :show] do
+ resources :rubric_items, except: [:index, :show]
+ end
resource :scoreboard, except: [:new]
resources :submissions, except: [:show] do
resources :annotations, only: [:create, :update, :destroy] do
diff --git a/db/migrate/20250426203028_create_rubric_items.rb b/db/migrate/20250426203028_create_rubric_items.rb
new file mode 100644
index 000000000..52c2d83cc
--- /dev/null
+++ b/db/migrate/20250426203028_create_rubric_items.rb
@@ -0,0 +1,13 @@
+class CreateRubricItems < ActiveRecord::Migration[6.1]
+ def change
+ create_table :rubric_items do |t|
+ t.references :problem, null: false, foreign_key: true, type: :integer
+ t.string :description, null: false
+ t.float :points, null: false
+ t.integer :order, null: false
+ t.timestamps
+ end
+
+ add_index :rubric_items, [:problem_id, :order], unique: true
+ end
+end
\ No newline at end of file
diff --git a/db/schema.rb b/db/schema.rb
index b212ea0a3..251733743 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,9 +10,9 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2024_04_06_174050) do
+ActiveRecord::Schema.define(version: 2025_04_26_203028) do
- create_table "active_storage_attachments", force: :cascade do |t|
+ create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
t.bigint "record_id", null: false
@@ -22,7 +22,7 @@
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
- create_table "active_storage_blobs", force: :cascade do |t|
+ create_table "active_storage_blobs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
@@ -34,13 +34,13 @@
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
- create_table "active_storage_variant_records", force: :cascade do |t|
+ create_table "active_storage_variant_records", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.bigint "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
- create_table "annotations", force: :cascade do |t|
+ create_table "annotations", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "submission_id"
t.string "filename"
t.integer "position"
@@ -56,11 +56,11 @@
t.boolean "global_comment", default: false
end
- create_table "announcements", force: :cascade do |t|
+ create_table "announcements", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "title"
t.text "description"
- t.datetime "start_date"
- t.datetime "end_date"
+ t.timestamp "start_date"
+ t.timestamp "end_date"
t.integer "course_user_datum_id"
t.integer "course_id"
t.datetime "created_at"
@@ -69,7 +69,7 @@
t.boolean "system", default: false, null: false
end
- create_table "assessment_user_data", force: :cascade do |t|
+ create_table "assessment_user_data", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "course_user_datum_id", null: false
t.integer "assessment_id", null: false
t.integer "latest_submission_id"
@@ -85,10 +85,10 @@
t.index ["latest_submission_id"], name: "index_assessment_user_data_on_latest_submission_id", unique: true
end
- create_table "assessments", force: :cascade do |t|
- t.datetime "due_at"
- t.datetime "end_at"
- t.datetime "start_at"
+ create_table "assessments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ t.timestamp "due_at"
+ t.timestamp "end_at"
+ t.timestamp "start_at"
t.string "name"
t.text "description"
t.datetime "created_at"
@@ -121,7 +121,7 @@
t.boolean "disable_network", default: false
end
- create_table "attachments", force: :cascade do |t|
+ create_table "attachments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "filename"
t.string "mime_type"
t.string "name"
@@ -136,7 +136,7 @@
t.index ["slug"], name: "index_attachments_on_slug", unique: true
end
- create_table "authentications", force: :cascade do |t|
+ create_table "authentications", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "provider", null: false
t.string "uid", null: false
t.integer "user_id"
@@ -144,14 +144,14 @@
t.datetime "updated_at"
end
- create_table "autograders", force: :cascade do |t|
+ create_table "autograders", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "assessment_id"
t.integer "autograde_timeout"
t.string "autograde_image"
t.boolean "release_score"
end
- create_table "course_user_data", force: :cascade do |t|
+ create_table "course_user_data", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "lecture"
t.string "section", default: ""
t.string "grade_policy", default: ""
@@ -167,7 +167,7 @@
t.string "course_number", default: ""
end
- create_table "courses", force: :cascade do |t|
+ create_table "courses", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name"
t.string "semester"
t.integer "late_slack"
@@ -187,14 +187,14 @@
t.boolean "disable_on_end", default: false
end
- create_table "extensions", force: :cascade do |t|
+ create_table "extensions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "course_user_datum_id"
t.integer "assessment_id"
t.integer "days"
t.boolean "infinite", default: false, null: false
end
- create_table "friendly_id_slugs", charset: "utf8mb3", force: :cascade do |t|
+ create_table "friendly_id_slugs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "slug", null: false
t.integer "sluggable_id", null: false
t.string "sluggable_type", limit: 50
@@ -205,7 +205,7 @@
t.index ["sluggable_type", "sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_type_and_sluggable_id"
end
- create_table "github_integrations", force: :cascade do |t|
+ create_table "github_integrations", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "oauth_state"
t.text "access_token_ciphertext"
t.integer "user_id"
@@ -215,37 +215,37 @@
t.index ["user_id"], name: "index_github_integrations_on_user_id", unique: true
end
- create_table "groups", force: :cascade do |t|
+ create_table "groups", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
- create_table "lti_course_data", force: :cascade do |t|
+ create_table "lti_course_data", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "context_id"
t.integer "course_id"
t.datetime "last_synced"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
t.string "membership_url"
t.string "platform"
t.boolean "auto_sync", default: false
t.boolean "drop_missing_students", default: false
end
- create_table "module_data", force: :cascade do |t|
+ create_table "module_data", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "field_id"
t.integer "data_id"
t.binary "data"
end
- create_table "module_fields", force: :cascade do |t|
+ create_table "module_fields", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "user_module_id"
t.string "name"
t.string "data_type"
end
- create_table "oauth_access_grants", force: :cascade do |t|
+ create_table "oauth_access_grants", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "resource_owner_id", null: false
t.integer "application_id", null: false
t.string "token", null: false
@@ -254,10 +254,11 @@
t.datetime "created_at", null: false
t.datetime "revoked_at"
t.string "scopes"
+ t.index ["application_id"], name: "fk_rails_b4b53e07b8"
t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true
end
- create_table "oauth_access_tokens", force: :cascade do |t|
+ create_table "oauth_access_tokens", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "resource_owner_id"
t.integer "application_id"
t.string "token", null: false
@@ -267,12 +268,13 @@
t.datetime "created_at", null: false
t.string "scopes"
t.string "previous_refresh_token", default: "", null: false
+ t.index ["application_id"], name: "fk_rails_732cb83ab7"
t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true
t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id"
t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true
end
- create_table "oauth_applications", force: :cascade do |t|
+ create_table "oauth_applications", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name", null: false
t.string "uid", null: false
t.string "secret", null: false
@@ -284,7 +286,7 @@
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
end
- create_table "oauth_device_flow_requests", force: :cascade do |t|
+ create_table "oauth_device_flow_requests", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "application_id", null: false
t.string "scopes", default: "", null: false
t.string "device_code", null: false
@@ -294,11 +296,12 @@
t.datetime "resolved_at"
t.integer "resource_owner_id"
t.string "access_code"
+ t.index ["application_id"], name: "fk_rails_4035c6e0ed"
t.index ["device_code"], name: "index_oauth_device_flow_requests_on_device_code", unique: true
t.index ["user_code"], name: "index_oauth_device_flow_requests_on_user_code", unique: true
end
- create_table "problems", force: :cascade do |t|
+ create_table "problems", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "name"
t.text "description"
t.integer "assessment_id"
@@ -310,7 +313,7 @@
t.index ["assessment_id", "name"], name: "problem_uniq", unique: true
end
- create_table "risk_conditions", force: :cascade do |t|
+ create_table "risk_conditions", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "condition_type"
t.text "parameters"
t.integer "version"
@@ -319,9 +322,20 @@
t.integer "course_id"
end
- create_table "scheduler", force: :cascade do |t|
+ create_table "rubric_items", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ t.integer "problem_id", null: false
+ t.string "description", null: false
+ t.float "points", null: false
+ t.integer "order", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["problem_id", "order"], name: "index_rubric_items_on_problem_id_and_order", unique: true
+ t.index ["problem_id"], name: "index_rubric_items_on_problem_id"
+ end
+
+ create_table "scheduler", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "action"
- t.datetime "next"
+ t.timestamp "next"
t.integer "interval"
t.integer "course_id"
t.datetime "created_at"
@@ -330,20 +344,20 @@
t.boolean "disabled", default: false
end
- create_table "score_adjustments", force: :cascade do |t|
+ create_table "score_adjustments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "kind", null: false
t.float "value", null: false
t.string "type", default: "Tweak", null: false
end
- create_table "scoreboards", force: :cascade do |t|
+ create_table "scoreboards", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "assessment_id"
t.text "banner"
t.text "colspec"
t.boolean "include_instructors", default: false
end
- create_table "scores", force: :cascade do |t|
+ create_table "scores", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "submission_id"
t.float "score"
t.text "feedback", size: :medium
@@ -356,7 +370,7 @@
t.index ["submission_id"], name: "index_scores_on_submission_id"
end
- create_table "submissions", force: :cascade do |t|
+ create_table "submissions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.integer "version"
t.integer "course_user_datum_id"
t.integer "assessment_id"
@@ -382,7 +396,7 @@
t.index ["course_user_datum_id"], name: "index_submissions_on_course_user_datum_id"
end
- create_table "users", force: :cascade do |t|
+ create_table "users", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "first_name", default: "", null: false
t.string "last_name", default: "", null: false
@@ -411,20 +425,20 @@
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
- create_table "watchlist_configurations", force: :cascade do |t|
+ create_table "watchlist_configurations", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
t.json "category_blocklist"
t.json "assessment_blocklist"
- t.integer "course_id"
+ t.bigint "course_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.boolean "allow_ca", default: false
t.index ["course_id"], name: "index_watchlist_configurations_on_course_id"
end
- create_table "watchlist_instances", force: :cascade do |t|
- t.integer "course_user_datum_id"
- t.integer "course_id"
- t.integer "risk_condition_id"
+ create_table "watchlist_instances", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ t.bigint "course_user_datum_id"
+ t.bigint "course_id"
+ t.bigint "risk_condition_id"
t.integer "status", default: 0
t.boolean "archived", default: false
t.datetime "created_at", null: false
@@ -437,4 +451,9 @@
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
+ add_foreign_key "github_integrations", "users"
+ add_foreign_key "oauth_access_grants", "oauth_applications", column: "application_id"
+ add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id"
+ add_foreign_key "oauth_device_flow_requests", "oauth_applications", column: "application_id"
+ add_foreign_key "rubric_items", "problems"
end
From d7384454e70274796099a47165fe12414ec510ac Mon Sep 17 00:00:00 2001
From: Justin Zou
Date: Sun, 4 May 2025 18:39:06 -0400
Subject: [PATCH 2/7] added in a new Spacebar ui change
---
Gemfile.lock | 1 +
app/assets/stylesheets/gradesheet.css.scss | 25 +++--
db/schema.rb | 115 ++++++++++-----------
3 files changed, 72 insertions(+), 69 deletions(-)
diff --git a/Gemfile.lock b/Gemfile.lock
index 37120e5a4..9cb89e98f 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -470,6 +470,7 @@ PLATFORMS
arm64-darwin-21
arm64-darwin-22
arm64-darwin-23
+ arm64-darwin-24
x86_64-darwin-19
x86_64-darwin-20
x86_64-darwin-21
diff --git a/app/assets/stylesheets/gradesheet.css.scss b/app/assets/stylesheets/gradesheet.css.scss
index 4236243ce..4a6b9bf5a 100755
--- a/app/assets/stylesheets/gradesheet.css.scss
+++ b/app/assets/stylesheets/gradesheet.css.scss
@@ -9,10 +9,11 @@ td.focus {
width: 90%;
max-width: none;
}
-
#grades_wrapper .tools {
- margin-top: 15px;
- margin-bottom: 20px;
+ display: flex;
+ justify-content: left;
+ padding-bottom: 0;
+ margin-bottom: 0;
}
#grades_wrapper .tools div {
@@ -57,15 +58,19 @@ td.focus {
}
#grades_filter input {
- border: 1px solid #d0d0d0;
- border-radius: 3px;
- box-shadow: 0 0 2px $autolab-highlight-gray;
- font-size: 18px;
- padding: 6px 12px;
- width: 100%;
- max-width: 400px;
+ margin-bottom: 0;
+ padding-bottom: 0;
+ font-size: 14px;
+ border: none;
+ border-radius: 0;
+ border-bottom: 2px solid #d9534f;
+ box-shadow: none;
+ outline: none;
+ width: 300px;
+ color: #333;
}
+
#grades td.edit input {
border: 0 solid $autolab-highlight-gray;
color: #e1e1e1;
diff --git a/db/schema.rb b/db/schema.rb
index 251733743..2d99ddb47 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -12,35 +12,35 @@
ActiveRecord::Schema.define(version: 2025_04_26_203028) do
- create_table "active_storage_attachments", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
- t.bigint "record_id", null: false
- t.bigint "blob_id", null: false
+ t.integer "record_id", null: false
+ t.integer "blob_id", null: false
t.datetime "created_at", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
end
- create_table "active_storage_blobs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "active_storage_blobs", force: :cascade do |t|
t.string "key", null: false
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
- t.bigint "byte_size", null: false
+ t.integer "byte_size", null: false
t.string "checksum", null: false
t.datetime "created_at", null: false
t.string "service_name", null: false
t.index ["key"], name: "index_active_storage_blobs_on_key", unique: true
end
- create_table "active_storage_variant_records", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
- t.bigint "blob_id", null: false
+ create_table "active_storage_variant_records", force: :cascade do |t|
+ t.integer "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
- create_table "annotations", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "annotations", force: :cascade do |t|
t.integer "submission_id"
t.string "filename"
t.integer "position"
@@ -56,11 +56,11 @@
t.boolean "global_comment", default: false
end
- create_table "announcements", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "announcements", force: :cascade do |t|
t.string "title"
t.text "description"
- t.timestamp "start_date"
- t.timestamp "end_date"
+ t.datetime "start_date"
+ t.datetime "end_date"
t.integer "course_user_datum_id"
t.integer "course_id"
t.datetime "created_at"
@@ -69,7 +69,7 @@
t.boolean "system", default: false, null: false
end
- create_table "assessment_user_data", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "assessment_user_data", force: :cascade do |t|
t.integer "course_user_datum_id", null: false
t.integer "assessment_id", null: false
t.integer "latest_submission_id"
@@ -85,10 +85,10 @@
t.index ["latest_submission_id"], name: "index_assessment_user_data_on_latest_submission_id", unique: true
end
- create_table "assessments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
- t.timestamp "due_at"
- t.timestamp "end_at"
- t.timestamp "start_at"
+ create_table "assessments", force: :cascade do |t|
+ t.datetime "due_at"
+ t.datetime "end_at"
+ t.datetime "start_at"
t.string "name"
t.text "description"
t.datetime "created_at"
@@ -121,7 +121,7 @@
t.boolean "disable_network", default: false
end
- create_table "attachments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "attachments", force: :cascade do |t|
t.string "filename"
t.string "mime_type"
t.string "name"
@@ -130,13 +130,13 @@
t.integer "course_id"
t.integer "assessment_id"
t.string "category_name", default: "General"
- t.datetime "release_at", default: -> { "CURRENT_TIMESTAMP" }
+ t.datetime "release_at"
t.string "slug"
t.index ["assessment_id"], name: "index_attachments_on_assessment_id"
t.index ["slug"], name: "index_attachments_on_slug", unique: true
end
- create_table "authentications", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "authentications", force: :cascade do |t|
t.string "provider", null: false
t.string "uid", null: false
t.integer "user_id"
@@ -144,14 +144,14 @@
t.datetime "updated_at"
end
- create_table "autograders", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "autograders", force: :cascade do |t|
t.integer "assessment_id"
t.integer "autograde_timeout"
t.string "autograde_image"
t.boolean "release_score"
end
- create_table "course_user_data", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "course_user_data", force: :cascade do |t|
t.string "lecture"
t.string "section", default: ""
t.string "grade_policy", default: ""
@@ -167,7 +167,7 @@
t.string "course_number", default: ""
end
- create_table "courses", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "courses", force: :cascade do |t|
t.string "name"
t.string "semester"
t.integer "late_slack"
@@ -187,25 +187,25 @@
t.boolean "disable_on_end", default: false
end
- create_table "extensions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "extensions", force: :cascade do |t|
t.integer "course_user_datum_id"
t.integer "assessment_id"
t.integer "days"
t.boolean "infinite", default: false, null: false
end
- create_table "friendly_id_slugs", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "friendly_id_slugs", force: :cascade do |t|
t.string "slug", null: false
t.integer "sluggable_id", null: false
t.string "sluggable_type", limit: 50
t.string "scope"
t.datetime "created_at"
- t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true, length: { slug: 70, scope: 70 }
- t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type", length: { slug: 140 }
+ t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true
+ t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type"
t.index ["sluggable_type", "sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_type_and_sluggable_id"
end
- create_table "github_integrations", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "github_integrations", force: :cascade do |t|
t.string "oauth_state"
t.text "access_token_ciphertext"
t.integer "user_id"
@@ -215,13 +215,13 @@
t.index ["user_id"], name: "index_github_integrations_on_user_id", unique: true
end
- create_table "groups", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "groups", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
- create_table "lti_course_data", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "lti_course_data", force: :cascade do |t|
t.string "context_id"
t.integer "course_id"
t.datetime "last_synced"
@@ -233,19 +233,19 @@
t.boolean "drop_missing_students", default: false
end
- create_table "module_data", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "module_data", force: :cascade do |t|
t.integer "field_id"
t.integer "data_id"
t.binary "data"
end
- create_table "module_fields", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "module_fields", force: :cascade do |t|
t.integer "user_module_id"
t.string "name"
t.string "data_type"
end
- create_table "oauth_access_grants", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "oauth_access_grants", force: :cascade do |t|
t.integer "resource_owner_id", null: false
t.integer "application_id", null: false
t.string "token", null: false
@@ -254,11 +254,10 @@
t.datetime "created_at", null: false
t.datetime "revoked_at"
t.string "scopes"
- t.index ["application_id"], name: "fk_rails_b4b53e07b8"
t.index ["token"], name: "index_oauth_access_grants_on_token", unique: true
end
- create_table "oauth_access_tokens", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "oauth_access_tokens", force: :cascade do |t|
t.integer "resource_owner_id"
t.integer "application_id"
t.string "token", null: false
@@ -268,13 +267,12 @@
t.datetime "created_at", null: false
t.string "scopes"
t.string "previous_refresh_token", default: "", null: false
- t.index ["application_id"], name: "fk_rails_732cb83ab7"
t.index ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true
t.index ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id"
t.index ["token"], name: "index_oauth_access_tokens_on_token", unique: true
end
- create_table "oauth_applications", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "oauth_applications", force: :cascade do |t|
t.string "name", null: false
t.string "uid", null: false
t.string "secret", null: false
@@ -286,7 +284,7 @@
t.index ["uid"], name: "index_oauth_applications_on_uid", unique: true
end
- create_table "oauth_device_flow_requests", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "oauth_device_flow_requests", force: :cascade do |t|
t.integer "application_id", null: false
t.string "scopes", default: "", null: false
t.string "device_code", null: false
@@ -296,12 +294,11 @@
t.datetime "resolved_at"
t.integer "resource_owner_id"
t.string "access_code"
- t.index ["application_id"], name: "fk_rails_4035c6e0ed"
t.index ["device_code"], name: "index_oauth_device_flow_requests_on_device_code", unique: true
t.index ["user_code"], name: "index_oauth_device_flow_requests_on_user_code", unique: true
end
- create_table "problems", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "problems", force: :cascade do |t|
t.string "name"
t.text "description"
t.integer "assessment_id"
@@ -313,7 +310,7 @@
t.index ["assessment_id", "name"], name: "problem_uniq", unique: true
end
- create_table "risk_conditions", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "risk_conditions", force: :cascade do |t|
t.integer "condition_type"
t.text "parameters"
t.integer "version"
@@ -322,7 +319,7 @@
t.integer "course_id"
end
- create_table "rubric_items", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "rubric_items", force: :cascade do |t|
t.integer "problem_id", null: false
t.string "description", null: false
t.float "points", null: false
@@ -333,34 +330,34 @@
t.index ["problem_id"], name: "index_rubric_items_on_problem_id"
end
- create_table "scheduler", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "scheduler", force: :cascade do |t|
t.string "action"
- t.timestamp "next"
+ t.datetime "next"
t.integer "interval"
t.integer "course_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.datetime "until", default: -> { "CURRENT_TIMESTAMP" }
+ t.datetime "until"
t.boolean "disabled", default: false
end
- create_table "score_adjustments", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "score_adjustments", force: :cascade do |t|
t.integer "kind", null: false
t.float "value", null: false
t.string "type", default: "Tweak", null: false
end
- create_table "scoreboards", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "scoreboards", force: :cascade do |t|
t.integer "assessment_id"
- t.text "banner"
- t.text "colspec"
+ t.text "banner", limit: 65535
+ t.text "colspec", limit: 65535
t.boolean "include_instructors", default: false
end
- create_table "scores", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "scores", force: :cascade do |t|
t.integer "submission_id"
t.float "score"
- t.text "feedback", size: :medium
+ t.text "feedback", limit: 16777215
t.integer "problem_id"
t.datetime "created_at"
t.datetime "updated_at"
@@ -370,7 +367,7 @@
t.index ["submission_id"], name: "index_scores_on_submission_id"
end
- create_table "submissions", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "submissions", force: :cascade do |t|
t.integer "version"
t.integer "course_user_datum_id"
t.integer "assessment_id"
@@ -386,7 +383,7 @@
t.string "submitter_ip", limit: 40
t.integer "tweak_id"
t.boolean "ignored", default: false, null: false
- t.string "dave"
+ t.string "dave", limit: 255
t.text "embedded_quiz_form_answer"
t.integer "submitted_by_app_id"
t.string "group_key", default: ""
@@ -396,7 +393,7 @@
t.index ["course_user_datum_id"], name: "index_submissions_on_course_user_datum_id"
end
- create_table "users", id: :integer, charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "first_name", default: "", null: false
t.string "last_name", default: "", null: false
@@ -425,20 +422,20 @@
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
- create_table "watchlist_configurations", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
+ create_table "watchlist_configurations", force: :cascade do |t|
t.json "category_blocklist"
t.json "assessment_blocklist"
- t.bigint "course_id"
+ t.integer "course_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.boolean "allow_ca", default: false
t.index ["course_id"], name: "index_watchlist_configurations_on_course_id"
end
- create_table "watchlist_instances", charset: "utf8mb4", collation: "utf8mb4_0900_ai_ci", force: :cascade do |t|
- t.bigint "course_user_datum_id"
- t.bigint "course_id"
- t.bigint "risk_condition_id"
+ create_table "watchlist_instances", force: :cascade do |t|
+ t.integer "course_user_datum_id"
+ t.integer "course_id"
+ t.integer "risk_condition_id"
t.integer "status", default: 0
t.boolean "archived", default: false
t.datetime "created_at", null: false
From 8ba073134ab9dcc60734ea3e8ad6c05e366623d5 Mon Sep 17 00:00:00 2001
From: Justin Zou
Date: Sun, 4 May 2025 21:06:06 -0400
Subject: [PATCH 3/7] updates to make the search filtering system faster and to
render the search bar with the appropriate library
---
app/assets/javascripts/gradesheet.js.erb | 32 ++++++++---------
app/assets/stylesheets/gradesheet.css.scss | 34 +++++++++++--------
app/views/assessments/viewGradesheet.html.erb | 8 +++++
3 files changed, 42 insertions(+), 32 deletions(-)
diff --git a/app/assets/javascripts/gradesheet.js.erb b/app/assets/javascripts/gradesheet.js.erb
index 8efac3891..37e501a55 100755
--- a/app/assets/javascripts/gradesheet.js.erb
+++ b/app/assets/javascripts/gradesheet.js.erb
@@ -118,10 +118,9 @@ jQuery(function() {
// main score table
var oTable = jQuery("#grades").dataTable({
'data' : table_data,
- 'sDom' : '<"tools"f>t', // '<"tools"fC>t', for individual problem column hide/show
+ 'sDom' : 't', // '<"tools"fC>t', for individual problem column hide/show
'bInfo': false,
'bPaginate': true,
- 'oLanguage': { "sSearch": "" },
'iTabIndex': -1,
'iDisplayLength': rows_on_display,
'iDisplayStart': 0,
@@ -148,6 +147,19 @@ jQuery(function() {
"aaSorting": [[ email_col, 'asc' ],[lec_sec_col, 'asc']]
}).fnSetFilteringDelay();
+ $('#filter').on('keyup', function() {
+ oTable.fnFilter(this.value);
+ });
+
+ $('#filter').on('keydown', function(event) {
+ if (event.keyCode === 13) { // return
+ asap(function() { jQuery(focusser).focus(); });
+ } else if (event.keyCode === 27) { // esc
+ event.preventDefault();
+ jQuery(this).val("");
+ }
+ });
+
// add sorting icons to table headings
function add_icons(selector) {
let icons = $icon_template.clone();
@@ -187,10 +199,6 @@ jQuery(function() {
}
})
- // placeholder text in Search field
- jQuery("#grades_filter input").attr("placeholder", "Search");
-
-
// get enclosing editor from inside of it
function get_enclosing_editor(el) {
return jQuery(el).closest('td.edit');
@@ -501,18 +509,6 @@ jQuery(function() {
$(event.target).data('changed', !$(event.target).data('changed'));
});
-
- jQuery("#grades_filter input").keydown(function(event){
- if (event.keyCode === 13) { // return
- asap(function() { jQuery(focusser).focus(); });
- } else if (event.keyCode === 27) { // esc
- event.preventDefault();
- jQuery(this).val("");
- }
- });
-
- jQuery('#grades_filter input').focus()
-
jQuery(document).click(function(event) {
close_current_editor_on_blur(event);
close_current_popover_on_blur(event);
diff --git a/app/assets/stylesheets/gradesheet.css.scss b/app/assets/stylesheets/gradesheet.css.scss
index 4a6b9bf5a..508369866 100755
--- a/app/assets/stylesheets/gradesheet.css.scss
+++ b/app/assets/stylesheets/gradesheet.css.scss
@@ -9,11 +9,21 @@ td.focus {
width: 90%;
max-width: none;
}
+
#grades_wrapper .tools {
+ margin-top: 15px;
+ margin-bottom: 20px;
+}
+
+div.filter_container.search-student-bar {
display: flex;
- justify-content: left;
- padding-bottom: 0;
- margin-bottom: 0;
+ flex-direction: row;
+ align-items: center;
+}
+
+div.filter_container.search-student-bar input {
+ padding-left: 8px;
+ height: 3rem;
}
#grades_wrapper .tools div {
@@ -58,19 +68,15 @@ td.focus {
}
#grades_filter input {
- margin-bottom: 0;
- padding-bottom: 0;
- font-size: 14px;
- border: none;
- border-radius: 0;
- border-bottom: 2px solid #d9534f;
- box-shadow: none;
- outline: none;
- width: 300px;
- color: #333;
+ border: 1px solid #d0d0d0;
+ border-radius: 3px;
+ box-shadow: 0 0 2px $autolab-highlight-gray;
+ font-size: 18px;
+ padding: 6px 12px;
+ width: 100%;
+ max-width: 400px;
}
-
#grades td.edit input {
border: 0 solid $autolab-highlight-gray;
color: #e1e1e1;
diff --git a/app/views/assessments/viewGradesheet.html.erb b/app/views/assessments/viewGradesheet.html.erb
index 68738885a..50c2a0df9 100755
--- a/app/views/assessments/viewGradesheet.html.erb
+++ b/app/views/assessments/viewGradesheet.html.erb
@@ -280,6 +280,14 @@ Interaction between gradesheet and annotations
<% end %>
+
+
+
From 6ccf22b617ddff91f14985655a8717a99923e823 Mon Sep 17 00:00:00 2001
From: Aryan Jain
Date: Mon, 5 May 2025 14:09:15 -0400
Subject: [PATCH 4/7] Revert "add basic structure for creating rubrics for
problems"
This reverts commit 184e4c2b2b4480202ded95456bdf0aa6e36ee15a.
---
app/assets/javascripts/rubric_items.js | 114 ------------------
app/controllers/rubric_items_controller.rb | 68 -----------
app/models/problem.rb | 1 -
app/models/rubric_item.rb | 10 --
app/views/problems/_fields.html.erb | 71 +----------
app/views/rubric_items/edit.html.erb | 22 ----
app/views/rubric_items/new.html.erb | 17 ---
config/routes.rb | 5 +-
.../20250426203028_create_rubric_items.rb | 13 --
db/schema.rb | 50 +++-----
10 files changed, 20 insertions(+), 351 deletions(-)
delete mode 100644 app/assets/javascripts/rubric_items.js
delete mode 100644 app/controllers/rubric_items_controller.rb
delete mode 100644 app/models/rubric_item.rb
delete mode 100644 app/views/rubric_items/edit.html.erb
delete mode 100644 app/views/rubric_items/new.html.erb
delete mode 100644 db/migrate/20250426203028_create_rubric_items.rb
diff --git a/app/assets/javascripts/rubric_items.js b/app/assets/javascripts/rubric_items.js
deleted file mode 100644
index c4a5e9e13..000000000
--- a/app/assets/javascripts/rubric_items.js
+++ /dev/null
@@ -1,114 +0,0 @@
-$(document).ready(function() {
- // Add new rubric item
- $('#add-rubric-item').click(function(e) {
- e.preventDefault();
- var newItem = createRubricItem();
- $('#rubric-items').append(newItem);
- M.updateTextFields();
- });
-
- // Delete rubric item
- $(document).on('click', '.delete-rubric-item', function(e) {
- e.preventDefault();
- var item = $(this).closest('.rubric-item');
- var id = item.data('id');
-
- if (id) {
- // If item exists in database, send delete request
- $.ajax({
- url: deletePath(id),
- method: 'DELETE',
- headers: {
- 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
- },
- success: function() {
- item.remove();
- M.toast({html: 'Rubric item deleted'});
- },
- error: function() {
- M.toast({html: 'Error deleting rubric item'});
- }
- });
- } else {
- // If item is new (no id), just remove from DOM
- item.remove();
- }
- });
-
- // Save rubric item changes
- $(document).on('change', '.rubric-description, .rubric-points', function() {
- var item = $(this).closest('.rubric-item');
- var id = item.data('id');
- var data = {
- description: item.find('.rubric-description').val(),
- points: item.find('.rubric-points').val(),
- order: item.index()
- };
- console.log("data", data, id, basePath, item);
- if (!data.description || !data.points) {
- M.toast({html: 'Description and points are required'});
- return;
- }
-
- if (id) {
- // Update existing item
- $.ajax({
- url: updatePath(id),
- method: 'PATCH',
- data: data,
- headers: {
- 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
- },
- success: function() {
- M.toast({html: 'Rubric item saved'});
- },
- error: function() {
- M.toast({html: 'Error saving rubric item'});
- }
- });
- } else {
- // Create new item
- data.problem_id = window.location.pathname.split('/')[4];
- $.ajax({
- url: createPath,
- method: 'POST',
- data: data,
- headers: {
- 'X-CSRF-Token': $('meta[name="csrf-token"]').attr('content')
- },
- success: function(response) {
- item.data('id', response.id);
- M.toast({html: 'Rubric item created'});
- },
- error: function() {
- M.toast({html: 'Error creating rubric item'});
- }
- });
- }
- });
-
- // Path helpers for AJAX requests
- var createPath = basePath + "/rubric_items";
- var updatePath = function(id) {
- return basePath + "/rubric_items/" + id;
- };
- var deletePath = updatePath;
-});
-
-function createRubricItem() {
- return `
-
-
-
-
-
-
-
-
-
- delete
-
-
-
- `;
-}
\ No newline at end of file
diff --git a/app/controllers/rubric_items_controller.rb b/app/controllers/rubric_items_controller.rb
deleted file mode 100644
index e66a0b7fc..000000000
--- a/app/controllers/rubric_items_controller.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-class RubricItemsController < ApplicationController
- before_action :set_assessment
- before_action :set_problem
- before_action :set_rubric_item, only: [:edit, :update, :destroy]
-
- action_auth_level :new, :instructor
- def new
- @rubric_item = @problem.rubric_items.new
- end
-
- action_auth_level :create, :instructor
- def create
- @rubric_item = @problem.rubric_items.new(rubric_item_params)
- @rubric_item.order = @problem.rubric_items.count
-
- if @rubric_item.save
- flash[:success] = "Rubric item created successfully"
- redirect_to edit_course_assessment_problem_path(@course, @assessment, @problem)
- else
- flash[:error] = "Error creating rubric item"
- @rubric_item.errors.full_messages.each do |msg|
- flash[:error] += " #{msg}"
- end
- flash[:html_safe] = true
- render :new
- end
- end
-
- action_auth_level :edit, :instructor
- def edit
- end
-
- action_auth_level :update, :instructor
- def update
- if @rubric_item.update(rubric_item_params)
- flash[:success] = "Rubric item updated successfully"
- redirect_to edit_course_assessment_problem_path(@course, @assessment, @problem)
- else
- flash[:error] = "Error updating rubric item"
- @rubric_item.errors.full_messages.each do |msg|
- flash[:error] += " #{msg}"
- end
- flash[:html_safe] = true
- render :edit
- end
- end
-
- action_auth_level :destroy, :instructor
- def destroy
- @rubric_item.destroy
- flash[:success] = "Rubric item deleted successfully"
- redirect_to edit_course_assessment_problem_path(@course, @assessment, @problem)
- end
-
- private
-
- def set_problem
- @problem = @assessment.problems.find(params[:problem_id])
- end
-
- def set_rubric_item
- @rubric_item = @problem.rubric_items.find(params[:id])
- end
-
- def rubric_item_params
- params.require(:rubric_item).permit(:description, :points, :order)
- end
-end
\ No newline at end of file
diff --git a/app/models/problem.rb b/app/models/problem.rb
index 23e93d443..bccdb1b3b 100755
--- a/app/models/problem.rb
+++ b/app/models/problem.rb
@@ -9,7 +9,6 @@ class Problem < ApplicationRecord
has_many :scores, dependent: :delete_all
belongs_to :assessment, touch: true
has_many :annotations, dependent: :destroy
- has_many :rubric_items, dependent: :destroy
validates :name, :max_score, presence: true
validates :name, uniqueness: { case_sensitive: false, scope: :assessment_id }
diff --git a/app/models/rubric_item.rb b/app/models/rubric_item.rb
deleted file mode 100644
index 1a8951262..000000000
--- a/app/models/rubric_item.rb
+++ /dev/null
@@ -1,10 +0,0 @@
-class RubricItem < ApplicationRecord
- belongs_to :problem
-
- validates :description, :points, :order, presence: true
- validates :points, numericality: { greater_than_or_equal_to: 0 }
- validates :order, numericality: { only_integer: true, greater_than_or_equal_to: 0 }
- validates :order, uniqueness: { scope: :problem_id }
-
- default_scope { order(order: :asc) }
-end
\ No newline at end of file
diff --git a/app/views/problems/_fields.html.erb b/app/views/problems/_fields.html.erb
index f478b47cd..2d65455cf 100755
--- a/app/views/problems/_fields.html.erb
+++ b/app/views/problems/_fields.html.erb
@@ -1,10 +1,3 @@
-<% content_for :javascripts do %>
-
- <%= javascript_include_tag "rubric_items" %>
-<% end %>
-
<%= f.text_field :name, help_text: "Each problem has a name that is unique to the assessment.",
placeholder: "problem1", required: true %>
@@ -28,67 +21,6 @@ unchecked." %>
<%= f.check_box :starred, help_text: "By default, all problems are
\"not starred\". Starred problems are displayed first in the problems dropdown when creating annotations." %>
-<% if false %>
-<% if @problem %>
-
-
Rubric Items
-
- <% @problem.rubric_items.each do |item| %>
-
-
-
-
-
-
-
-
-
- delete
-
-
-
- <% end %>
-
-
Add Rubric Item
-
-<% end %>
-<% end %>
-
-<% if @problem %>
-
-
Rubric Items
-
-
-
- Description
- Points
- Actions
-
-
-
- <% @problem.rubric_items.each do |item| %>
-
- <%= item.description %>
- <%= item.points %>
-
- <%= link_to "mode_edit ".html_safe,
- edit_course_assessment_problem_rubric_item_path(@course, @assessment, @problem, item),
- { class: "small" } %>
- <%= link_to "delete ".html_safe,
- [@course, @assessment, @problem, item],
- method: :delete,
- class: "small",
- data: { confirm: "Are you sure you want to delete this rubric item?" } %>
-
-
- <% end %>
-
-
-
- <%= link_to "Add Rubric Item", new_course_assessment_problem_rubric_item_path(@course, @assessment, @problem), { class: "btn" } %>
-
-<% end %>
-
<%= f.submit "Save Problem", { class: "btn primary" } %>
<% if @problem %>
@@ -97,4 +29,5 @@ unchecked." %>
method: :delete, class: 'btn btn-danger',
data: { confirm: "Deleting will delete all associated problem data (such as scores and annotations) and cannot be undone. Are you sure you want to delete this problem?" } %>
- <% end %>
\ No newline at end of file
+ <% end %>
+
diff --git a/app/views/rubric_items/edit.html.erb b/app/views/rubric_items/edit.html.erb
deleted file mode 100644
index 231993568..000000000
--- a/app/views/rubric_items/edit.html.erb
+++ /dev/null
@@ -1,22 +0,0 @@
-<%# For navigation breadcrumbs %>
-<% @title = "Edit Rubric Item" %>
-
-<% content_for :stylesheets do %>
- <%= stylesheet_link_tag "problems" %>
-<% end %>
-
-Edit Rubric Item
-<%= form_for [@course, @assessment, @problem, @rubric_item], builder: FormBuilderWithDateTimeInput do |f| %>
- <%= f.text_field :description, help_text: "Description of the rubric item.", placeholder: "Description", required: true %>
-
- <%= f.number_field :points, help_text: "Points for this rubric item.", placeholder: "0", step: "any", required: true %>
-
-
- <%= f.submit "Save Rubric Item", { class: "btn primary" } %>
-
- <%= link_to "Delete this Rubric Item", [@course, @assessment, @problem, @rubric_item],
- method: :delete, class: 'btn btn-danger',
- data: { confirm: "Are you sure you want to delete this rubric item?" } %>
-
-
-<% end %>
\ No newline at end of file
diff --git a/app/views/rubric_items/new.html.erb b/app/views/rubric_items/new.html.erb
deleted file mode 100644
index fbef3f6db..000000000
--- a/app/views/rubric_items/new.html.erb
+++ /dev/null
@@ -1,17 +0,0 @@
-<%# For navigation breadcrumbs %>
-<% @title = "New Rubric Item" %>
-
-<% content_for :stylesheets do %>
- <%= stylesheet_link_tag "problems" %>
-<% end %>
-
-Create New Rubric Item
-<%= form_for [@course, @assessment, @problem, @rubric_item], builder: FormBuilderWithDateTimeInput do |f| %>
- <%= f.text_field :description, help_text: "Description of the rubric item.", placeholder: "Description", required: true %>
-
- <%= f.number_field :points, help_text: "Points for this rubric item.", placeholder: "0", step: "any", required: true %>
-
-
- <%= f.submit "Save Rubric Item", { class: "btn primary" } %>
-
-<% end %>
\ No newline at end of file
diff --git a/config/routes.rb b/config/routes.rb
index c41b94dad..d113b57ff 100755
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -155,10 +155,7 @@
post "import", on: :collection
end
- # resources :problems, except: [:index, :show]
- resources :problems, except: [:index, :show] do
- resources :rubric_items, except: [:index, :show]
- end
+ resources :problems, except: [:index, :show]
resource :scoreboard, except: [:new]
resources :submissions, except: [:show] do
resources :annotations, only: [:create, :update, :destroy] do
diff --git a/db/migrate/20250426203028_create_rubric_items.rb b/db/migrate/20250426203028_create_rubric_items.rb
deleted file mode 100644
index 52c2d83cc..000000000
--- a/db/migrate/20250426203028_create_rubric_items.rb
+++ /dev/null
@@ -1,13 +0,0 @@
-class CreateRubricItems < ActiveRecord::Migration[6.1]
- def change
- create_table :rubric_items do |t|
- t.references :problem, null: false, foreign_key: true, type: :integer
- t.string :description, null: false
- t.float :points, null: false
- t.integer :order, null: false
- t.timestamps
- end
-
- add_index :rubric_items, [:problem_id, :order], unique: true
- end
-end
\ No newline at end of file
diff --git a/db/schema.rb b/db/schema.rb
index 2d99ddb47..c13e89321 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,13 +10,13 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2025_04_26_203028) do
+ActiveRecord::Schema.define(version: 2024_04_06_174050) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
- t.integer "record_id", null: false
- t.integer "blob_id", null: false
+ t.bigint "record_id", null: false
+ t.bigint "blob_id", null: false
t.datetime "created_at", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
@@ -27,7 +27,7 @@
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
- t.integer "byte_size", null: false
+ t.bigint "byte_size", null: false
t.string "checksum", null: false
t.datetime "created_at", null: false
t.string "service_name", null: false
@@ -35,7 +35,7 @@
end
create_table "active_storage_variant_records", force: :cascade do |t|
- t.integer "blob_id", null: false
+ t.bigint "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
@@ -130,7 +130,7 @@
t.integer "course_id"
t.integer "assessment_id"
t.string "category_name", default: "General"
- t.datetime "release_at"
+ t.datetime "release_at", default: -> { "CURRENT_TIMESTAMP" }
t.string "slug"
t.index ["assessment_id"], name: "index_attachments_on_assessment_id"
t.index ["slug"], name: "index_attachments_on_slug", unique: true
@@ -194,14 +194,14 @@
t.boolean "infinite", default: false, null: false
end
- create_table "friendly_id_slugs", force: :cascade do |t|
+ create_table "friendly_id_slugs", charset: "utf8mb3", force: :cascade do |t|
t.string "slug", null: false
t.integer "sluggable_id", null: false
t.string "sluggable_type", limit: 50
t.string "scope"
t.datetime "created_at"
- t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true
- t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type"
+ t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true, length: { slug: 70, scope: 70 }
+ t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type", length: { slug: 140 }
t.index ["sluggable_type", "sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_type_and_sluggable_id"
end
@@ -225,8 +225,8 @@
t.string "context_id"
t.integer "course_id"
t.datetime "last_synced"
- t.datetime "created_at", precision: 6, null: false
- t.datetime "updated_at", precision: 6, null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
t.string "membership_url"
t.string "platform"
t.boolean "auto_sync", default: false
@@ -319,17 +319,6 @@
t.integer "course_id"
end
- create_table "rubric_items", force: :cascade do |t|
- t.integer "problem_id", null: false
- t.string "description", null: false
- t.float "points", null: false
- t.integer "order", null: false
- t.datetime "created_at", precision: 6, null: false
- t.datetime "updated_at", precision: 6, null: false
- t.index ["problem_id", "order"], name: "index_rubric_items_on_problem_id_and_order", unique: true
- t.index ["problem_id"], name: "index_rubric_items_on_problem_id"
- end
-
create_table "scheduler", force: :cascade do |t|
t.string "action"
t.datetime "next"
@@ -337,7 +326,7 @@
t.integer "course_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.datetime "until"
+ t.datetime "until", default: -> { "CURRENT_TIMESTAMP" }
t.boolean "disabled", default: false
end
@@ -349,15 +338,15 @@
create_table "scoreboards", force: :cascade do |t|
t.integer "assessment_id"
- t.text "banner", limit: 65535
- t.text "colspec", limit: 65535
+ t.text "banner"
+ t.text "colspec"
t.boolean "include_instructors", default: false
end
create_table "scores", force: :cascade do |t|
t.integer "submission_id"
t.float "score"
- t.text "feedback", limit: 16777215
+ t.text "feedback", size: :medium
t.integer "problem_id"
t.datetime "created_at"
t.datetime "updated_at"
@@ -383,7 +372,7 @@
t.string "submitter_ip", limit: 40
t.integer "tweak_id"
t.boolean "ignored", default: false, null: false
- t.string "dave", limit: 255
+ t.string "dave"
t.text "embedded_quiz_form_answer"
t.integer "submitted_by_app_id"
t.string "group_key", default: ""
@@ -448,9 +437,4 @@
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
- add_foreign_key "github_integrations", "users"
- add_foreign_key "oauth_access_grants", "oauth_applications", column: "application_id"
- add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id"
- add_foreign_key "oauth_device_flow_requests", "oauth_applications", column: "application_id"
- add_foreign_key "rubric_items", "problems"
-end
+end
\ No newline at end of file
From e4e8175c8a50b4f67fd2646b941c2e4728e9daf7 Mon Sep 17 00:00:00 2001
From: Aryan Jain
Date: Mon, 5 May 2025 14:13:41 -0400
Subject: [PATCH 5/7] revert schema
---
db/schema.rb | 66 ++++++++++++++++++++++++++++++++++++++--------------
1 file changed, 49 insertions(+), 17 deletions(-)
diff --git a/db/schema.rb b/db/schema.rb
index c13e89321..7e5d7a428 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,13 +10,13 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2024_04_06_174050) do
+ActiveRecord::Schema.define(version: 2025_04_26_203028) do
create_table "active_storage_attachments", force: :cascade do |t|
t.string "name", null: false
t.string "record_type", null: false
- t.bigint "record_id", null: false
- t.bigint "blob_id", null: false
+ t.integer "record_id", null: false
+ t.integer "blob_id", null: false
t.datetime "created_at", null: false
t.index ["blob_id"], name: "index_active_storage_attachments_on_blob_id"
t.index ["record_type", "record_id", "name", "blob_id"], name: "index_active_storage_attachments_uniqueness", unique: true
@@ -27,7 +27,7 @@
t.string "filename", null: false
t.string "content_type"
t.text "metadata"
- t.bigint "byte_size", null: false
+ t.integer "byte_size", null: false
t.string "checksum", null: false
t.datetime "created_at", null: false
t.string "service_name", null: false
@@ -35,7 +35,7 @@
end
create_table "active_storage_variant_records", force: :cascade do |t|
- t.bigint "blob_id", null: false
+ t.integer "blob_id", null: false
t.string "variation_digest", null: false
t.index ["blob_id", "variation_digest"], name: "index_active_storage_variant_records_uniqueness", unique: true
end
@@ -54,6 +54,8 @@
t.string "coordinate"
t.boolean "shared_comment", default: false
t.boolean "global_comment", default: false
+ t.integer "rubric_item_id"
+ t.index ["rubric_item_id"], name: "index_annotations_on_rubric_item_id"
end
create_table "announcements", force: :cascade do |t|
@@ -130,7 +132,7 @@
t.integer "course_id"
t.integer "assessment_id"
t.string "category_name", default: "General"
- t.datetime "release_at", default: -> { "CURRENT_TIMESTAMP" }
+ t.datetime "release_at"
t.string "slug"
t.index ["assessment_id"], name: "index_attachments_on_assessment_id"
t.index ["slug"], name: "index_attachments_on_slug", unique: true
@@ -194,14 +196,14 @@
t.boolean "infinite", default: false, null: false
end
- create_table "friendly_id_slugs", charset: "utf8mb3", force: :cascade do |t|
+ create_table "friendly_id_slugs", force: :cascade do |t|
t.string "slug", null: false
t.integer "sluggable_id", null: false
t.string "sluggable_type", limit: 50
t.string "scope"
t.datetime "created_at"
- t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true, length: { slug: 70, scope: 70 }
- t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type", length: { slug: 140 }
+ t.index ["slug", "sluggable_type", "scope"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type_and_scope", unique: true
+ t.index ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type"
t.index ["sluggable_type", "sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_type_and_sluggable_id"
end
@@ -225,8 +227,8 @@
t.string "context_id"
t.integer "course_id"
t.datetime "last_synced"
- t.datetime "created_at", null: false
- t.datetime "updated_at", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
t.string "membership_url"
t.string "platform"
t.boolean "auto_sync", default: false
@@ -319,6 +321,28 @@
t.integer "course_id"
end
+ create_table "rubric_item_assignments", force: :cascade do |t|
+ t.integer "rubric_item_id", null: false
+ t.integer "submission_id", null: false
+ t.boolean "assigned", default: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["rubric_item_id", "submission_id"], name: "index_ria_on_rubric_item_id_and_submission_id", unique: true
+ t.index ["rubric_item_id"], name: "index_rubric_item_assignments_on_rubric_item_id"
+ t.index ["submission_id"], name: "index_rubric_item_assignments_on_submission_id"
+ end
+
+ create_table "rubric_items", force: :cascade do |t|
+ t.integer "problem_id", null: false
+ t.string "description", null: false
+ t.float "points", null: false
+ t.integer "order", null: false
+ t.datetime "created_at", precision: 6, null: false
+ t.datetime "updated_at", precision: 6, null: false
+ t.index ["problem_id", "order"], name: "index_rubric_items_on_problem_id_and_order", unique: true
+ t.index ["problem_id"], name: "index_rubric_items_on_problem_id"
+ end
+
create_table "scheduler", force: :cascade do |t|
t.string "action"
t.datetime "next"
@@ -326,7 +350,7 @@
t.integer "course_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.datetime "until", default: -> { "CURRENT_TIMESTAMP" }
+ t.datetime "until"
t.boolean "disabled", default: false
end
@@ -338,15 +362,15 @@
create_table "scoreboards", force: :cascade do |t|
t.integer "assessment_id"
- t.text "banner"
- t.text "colspec"
+ t.text "banner", limit: 65535
+ t.text "colspec", limit: 65535
t.boolean "include_instructors", default: false
end
create_table "scores", force: :cascade do |t|
t.integer "submission_id"
t.float "score"
- t.text "feedback", size: :medium
+ t.text "feedback", limit: 16777215
t.integer "problem_id"
t.datetime "created_at"
t.datetime "updated_at"
@@ -372,7 +396,7 @@
t.string "submitter_ip", limit: 40
t.integer "tweak_id"
t.boolean "ignored", default: false, null: false
- t.string "dave"
+ t.string "dave", limit: 255
t.text "embedded_quiz_form_answer"
t.integer "submitted_by_app_id"
t.string "group_key", default: ""
@@ -437,4 +461,12 @@
add_foreign_key "active_storage_attachments", "active_storage_blobs", column: "blob_id"
add_foreign_key "active_storage_variant_records", "active_storage_blobs", column: "blob_id"
-end
\ No newline at end of file
+ add_foreign_key "annotations", "rubric_items"
+ add_foreign_key "github_integrations", "users"
+ add_foreign_key "oauth_access_grants", "oauth_applications", column: "application_id"
+ add_foreign_key "oauth_access_tokens", "oauth_applications", column: "application_id"
+ add_foreign_key "oauth_device_flow_requests", "oauth_applications", column: "application_id"
+ add_foreign_key "rubric_item_assignments", "rubric_items"
+ add_foreign_key "rubric_item_assignments", "submissions"
+ add_foreign_key "rubric_items", "problems"
+end
From 67d83edec6baf55265c82e8cbafd17438c922392 Mon Sep 17 00:00:00 2001
From: Aryan Jain
Date: Mon, 5 May 2025 14:13:42 -0400
Subject: [PATCH 6/7] revert schema changes
---
Gemfile | 2 +-
Gemfile.lock | 4 ++--
libsqlite3.dylib | Bin 0 -> 1207216 bytes
3 files changed, 3 insertions(+), 3 deletions(-)
create mode 100755 libsqlite3.dylib
diff --git a/Gemfile b/Gemfile
index 45367d901..c4c4be3ea 100644
--- a/Gemfile
+++ b/Gemfile
@@ -49,7 +49,7 @@ gem 'rake', '>=10.3.2'
gem 'populator', '>=1.0.0'
# To communicate with MySQL database
-gem 'mysql2', '~>0.5'
+gem 'mysql2', '=0.5.4'
# Development server
gem 'thin'
diff --git a/Gemfile.lock b/Gemfile.lock
index 9cb89e98f..ea382c020 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -231,7 +231,7 @@ GEM
momentjs-rails (2.15.1)
railties (>= 3.1)
multi_xml (0.6.0)
- mysql2 (0.5.5)
+ mysql2 (0.5.4)
net-http (0.4.0)
uri
net-imap (0.4.9)
@@ -510,7 +510,7 @@ DEPENDENCIES
mini_racer (~> 0.6.3)
moment_timezone-rails
momentjs-rails (>= 2.9.0)
- mysql2 (~> 0.5)
+ mysql2 (= 0.5.4)
net-http
net-ldap
newrelic_rpm
diff --git a/libsqlite3.dylib b/libsqlite3.dylib
new file mode 100755
index 0000000000000000000000000000000000000000..41e88f962edb08efd16502d2c4458fc789cc9356
GIT binary patch
literal 1207216
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