From 224344cef5f051e4daae43c878ae55f236f01a12 Mon Sep 17 00:00:00 2001 From: "josh.talev" Date: Thu, 26 Mar 2026 22:57:40 +1100 Subject: [PATCH 1/3] adds time estimate to task definitions --- app/api/entities/task_definition_entity.rb | 8 ++++++++ app/api/task_definitions_api.rb | 20 +++++++++++++++++-- ...imated_time_minutes_to_task_definitions.rb | 9 +++++++++ db/schema.rb | 3 ++- 4 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 db/migrate/20260326104829_add_estimated_time_minutes_to_task_definitions.rb diff --git a/app/api/entities/task_definition_entity.rb b/app/api/entities/task_definition_entity.rb index 94ba180d48..e95be2527b 100644 --- a/app/api/entities/task_definition_entity.rb +++ b/app/api/entities/task_definition_entity.rb @@ -44,5 +44,13 @@ def staff?(my_role) expose :overseer_image_id, if: ->(unit, options) { staff?(options[:my_role]) } expose :assessment_enabled, if: ->(unit, options) { staff?(options[:my_role]) } expose :moss_language, if: ->(unit, options) { staff?(options[:my_role]) } + + expose :estimated_days do |task_def, options| + task_def.estimated_days || (task_def.estimated_time_minutes.to_i / 60 / 24) + end + + expose :estimated_hours do |task_def, options| + task_def.estimated_hours || ((task_def.estimated_time_minutes.to_i / 60) % 24) + end end end diff --git a/app/api/task_definitions_api.rb b/app/api/task_definitions_api.rb index 03536c9efd..b6d37b2b29 100644 --- a/app/api/task_definitions_api.rb +++ b/app/api/task_definitions_api.rb @@ -33,6 +33,8 @@ class TaskDefinitionsApi < Grape::API optional :assessment_enabled, type: Boolean, desc: 'Enable or disable assessment' optional :overseer_image_id, type: Integer, desc: 'The id of the Docker image for overseer' optional :moss_language, type: String, desc: 'The language to use for code similarity checks' + optional :estimated_days, type: Integer, desc: 'Estimated time to complete task, measured in days' + optional :estimated_hours, type: Integer, desc: 'Estimated time to complete task, measured in hours' end end post '/units/:unit_id/task_definitions/' do @@ -61,12 +63,18 @@ class TaskDefinitionsApi < Grape::API :max_quality_pts, :assessment_enabled, :overseer_image_id, - :moss_language + :moss_language, + :estimated_days, + :estimated_hours ) task_params[:unit_id] = unit.id task_params[:upload_requirements] = JSON.parse(params[:task_def][:upload_requirements]) unless params[:task_def][:upload_requirements].nil? + days = task_params.delete(:estimated_days).to_i + hours = task_params.delete(:estimated_hours).to_i + task_params[:estimated_time_minutes] = (days * 24 * 60) + (hours * 60) + task_def = TaskDefinition.new(task_params) # Set the tutorial stream @@ -111,6 +119,8 @@ class TaskDefinitionsApi < Grape::API optional :assessment_enabled, type: Boolean, desc: 'Enable or disable assessment' optional :overseer_image_id, type: Integer, desc: 'The id of the Docker image name for overseer' optional :moss_language, type: String, desc: 'The language to use for code similarity checks' + optional :estimated_days, type: Integer, desc: 'Estimated time to complete task, measured in days' + optional :estimated_hours, type: Integer, desc: 'Estimated time to complete task, measured in hours' end end put '/units/:unit_id/task_definitions/:id' do @@ -138,11 +148,17 @@ class TaskDefinitionsApi < Grape::API :max_quality_pts, :assessment_enabled, :overseer_image_id, - :moss_language + :moss_language, + :estimated_days, + :estimated_hours ) task_params[:upload_requirements] = JSON.parse(params[:task_def][:upload_requirements]) unless params[:task_def][:upload_requirements].nil? + days = task_params.delete(:estimated_days).to_i + hours = task_params.delete(:estimated_hours).to_i + task_params[:estimated_time_minutes] = (days * 24 * 60) + (hours * 60) + # Ensure changes to a TD defined as a "draft task definition" are validated if unit.draft_task_definition_id == params[:id] if params[:task_def][:upload_requirements] diff --git a/db/migrate/20260326104829_add_estimated_time_minutes_to_task_definitions.rb b/db/migrate/20260326104829_add_estimated_time_minutes_to_task_definitions.rb new file mode 100644 index 0000000000..6a1ada00ab --- /dev/null +++ b/db/migrate/20260326104829_add_estimated_time_minutes_to_task_definitions.rb @@ -0,0 +1,9 @@ +class AddEstimatedTimeMinutesToTaskDefinitions < ActiveRecord::Migration[7.1] + def up + add_column :task_definitions, :estimated_time_minutes, :integer, null: true, default: 0, comment: "Estimated time to complete task, measured in minutes" + end + + def down + remove_column :task_definitions, :estimated_time_minutes + end +end diff --git a/db/schema.rb b/db/schema.rb index 6daa71ebf1..2db9d5b444 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[7.1].define(version: 2024_05_28_223908) do +ActiveRecord::Schema[7.1].define(version: 2026_03_26_104829) do create_table "activity_types", charset: "utf8", collation: "utf8_unicode_ci", force: :cascade do |t| t.string "name", null: false t.string "abbreviation", null: false @@ -250,6 +250,7 @@ t.bigint "overseer_image_id" t.string "tii_group_id" t.string "moss_language" + t.integer "estimated_time_minutes", default: 0, comment: "Estimated time to complete task, measured in minutes" t.index ["group_set_id"], name: "index_task_definitions_on_group_set_id" t.index ["overseer_image_id"], name: "index_task_definitions_on_overseer_image_id" t.index ["tutorial_stream_id"], name: "index_task_definitions_on_tutorial_stream_id" From df20b77f6a23914042753687db3b8db7c679cfa7 Mon Sep 17 00:00:00 2001 From: "josh.talev" Date: Mon, 30 Mar 2026 21:30:49 +1100 Subject: [PATCH 2/3] adds estimated time to model and entity - task definition api tests updated to reflect changes --- app/api/entities/task_definition_entity.rb | 8 ++++---- app/api/task_definitions_api.rb | 1 + app/models/task_definition.rb | 8 ++++++++ test/api/units/task_definitions_api_test.rb | 18 +++++++++++++++--- 4 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/api/entities/task_definition_entity.rb b/app/api/entities/task_definition_entity.rb index e95be2527b..36f70ce172 100644 --- a/app/api/entities/task_definition_entity.rb +++ b/app/api/entities/task_definition_entity.rb @@ -45,12 +45,12 @@ def staff?(my_role) expose :assessment_enabled, if: ->(unit, options) { staff?(options[:my_role]) } expose :moss_language, if: ->(unit, options) { staff?(options[:my_role]) } - expose :estimated_days do |task_def, options| - task_def.estimated_days || (task_def.estimated_time_minutes.to_i / 60 / 24) + expose :estimated_days do |task_def, _options| + task_def.estimated_days end - expose :estimated_hours do |task_def, options| - task_def.estimated_hours || ((task_def.estimated_time_minutes.to_i / 60) % 24) + expose :estimated_hours do |task_def, _options| + task_def.estimated_hours end end end diff --git a/app/api/task_definitions_api.rb b/app/api/task_definitions_api.rb index b6d37b2b29..2880a8a45b 100644 --- a/app/api/task_definitions_api.rb +++ b/app/api/task_definitions_api.rb @@ -74,6 +74,7 @@ class TaskDefinitionsApi < Grape::API days = task_params.delete(:estimated_days).to_i hours = task_params.delete(:estimated_hours).to_i task_params[:estimated_time_minutes] = (days * 24 * 60) + (hours * 60) + Rails.logger.info "TASK_DEF PARAMS: #{params[:task_def].inspect}" task_def = TaskDefinition.new(task_params) diff --git a/app/models/task_definition.rb b/app/models/task_definition.rb index 7e78bd2a71..fa2303c085 100644 --- a/app/models/task_definition.rb +++ b/app/models/task_definition.rb @@ -485,6 +485,14 @@ def read_file_from_resources(filename) nil end + def estimated_days + estimated_time_minutes.to_i / 60 / 24 + end + + def estimated_hours + (estimated_time_minutes.to_i / 60) % 24 + end + private def delete_associated_files() diff --git a/test/api/units/task_definitions_api_test.rb b/test/api/units/task_definitions_api_test.rb index 656d2d67cd..65ccc34a79 100644 --- a/test/api/units/task_definitions_api_test.rb +++ b/test/api/units/task_definitions_api_test.rb @@ -24,7 +24,9 @@ def all_task_def_keys 'restrict_status_updates', 'plagiarism_warn_pct', 'is_graded', - 'max_quality_pts' + 'max_quality_pts', + 'estimated_days', + 'estimated_hours' ] end @@ -49,7 +51,9 @@ def test_task_definition_cud upload_requirements: '[ { "key": "file0", "name": "Shape Class", "type": "document" } ]', plagiarism_warn_pct: 80, is_graded: false, - max_quality_pts: 0 + max_quality_pts: 0, + estimated_days: 1, + estimated_hours: 0 } } @@ -65,6 +69,9 @@ def test_task_definition_cud assert_json_matches_model td, last_response_body, all_task_def_keys assert_equal unit.tutorial_streams.first.id, td.tutorial_stream_id assert_equal 4, td.weighting + assert_equal (24 * 60), td.estimated_time_minutes + assert_equal 1, last_response_body['estimated_days'] + assert_equal 0, last_response_body['estimated_hours'] data_to_put = { @@ -83,7 +90,9 @@ def test_task_definition_cud upload_requirements: [ { "key": "file0", "name": "Other Class", "type": "document" } ].to_json, plagiarism_warn_pct: 80, is_graded: false, - max_quality_pts: 0 + max_quality_pts: 0, + estimated_days: 2, + estimated_hours: 3 } } @@ -98,6 +107,9 @@ def test_task_definition_cud assert_json_matches_model td, last_response_body, all_task_def_keys assert_equal unit.tutorial_streams.last.id, td.tutorial_stream_id assert_equal 2, td.weighting + assert_equal 3060, td.estimated_time_minutes + assert_equal 2, last_response_body['estimated_days'] + assert_equal 3, last_response_body['estimated_hours'] end def test_post_invalid_file_tasksheet From 65c736cd0ebbcdc243aa3a790be0b8ce00ff9a27 Mon Sep 17 00:00:00 2001 From: "josh.talev" Date: Mon, 30 Mar 2026 21:34:34 +1100 Subject: [PATCH 3/3] remove debug logging --- app/api/task_definitions_api.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/app/api/task_definitions_api.rb b/app/api/task_definitions_api.rb index 2880a8a45b..b6d37b2b29 100644 --- a/app/api/task_definitions_api.rb +++ b/app/api/task_definitions_api.rb @@ -74,7 +74,6 @@ class TaskDefinitionsApi < Grape::API days = task_params.delete(:estimated_days).to_i hours = task_params.delete(:estimated_hours).to_i task_params[:estimated_time_minutes] = (days * 24 * 60) + (hours * 60) - Rails.logger.info "TASK_DEF PARAMS: #{params[:task_def].inspect}" task_def = TaskDefinition.new(task_params)