From 5e8a4f65d923dd5847ae564f8337be16fae0564c Mon Sep 17 00:00:00 2001 From: owens-hub-git Date: Sun, 10 May 2026 17:25:05 +1000 Subject: [PATCH 1/5] feature: new endpoint for crossunit team --- app/api/projects_api.rb | 56 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/app/api/projects_api.rb b/app/api/projects_api.rb index fd0d8b4868..db632511da 100644 --- a/app/api/projects_api.rb +++ b/app/api/projects_api.rb @@ -20,6 +20,62 @@ class ProjectsApi < Grape::API present projects, with: Entities::ProjectEntity, for_student: true, summary_only: true, user: current_user end + desc "Fetches all of the current user's projects with the tasks for crossunit dashboard" + get "/projects/crossunit" do + projects = Project.eager_load(:unit).for_user(current_user, false) + + result = projects.map do |project| + unit = project.unit + + # Index existing task records by their task_definition_id for quick lookup + existing_tasks = project.tasks.eager_load(:task_definition).index_by(&:task_definition_id) + + tasks = unit.task_definitions.map do |td| + task = existing_tasks[td.id] + { + id: task&.id, + task_definition_id: td.id, + abbreviation: td.abbreviation, + name: td.name, + description: td.description, + status: task ? TaskStatus.id_to_key(task.task_status_id) : 'not_started', + target_date: td.target_date, + due_date: td.due_date, + start_date: td.start_date, + submission_date: task&.submission_date, + completion_date: task&.completion_date, + target_grade: td.target_grade, + weighting: td.weighting, + upload_requirements: td.upload_requirements, + is_graded: td.is_graded, + max_quality_pts: td.max_quality_pts + } + end + + { + id: project.id, + campus_id: project.campus_id, + user_id: project.user_id, + target_grade: project.target_grade, + spec_con_days: project.spec_con_days, + portfolio_available: project.portfolio_available, + escalation_attempts_remaining: project.escalation_attempts_remaining, + unit: { + code: unit.code, + id: unit.id, + name: unit.name, + my_role: project.student == current_user ? 'Student' : 'Staff', + start_date: unit.start_date, + end_date: unit.end_date, + active: unit.active + }, + tasks: tasks + } + end + + present result, with: Grape::Presenters::Presenter + end + desc 'Get project' params do requires :id, type: Integer, desc: 'The id of the project to get' From a6e70ac57ba0de4cda0799ce467c24a758e415f7 Mon Sep 17 00:00:00 2001 From: owens-hub-git Date: Mon, 11 May 2026 15:48:44 +1000 Subject: [PATCH 2/5] fix: for previous review that requested changes so now it's via the same projects endpoint --- app/api/entities/project_entity.rb | 4 ++ app/api/projects_api.rb | 69 ++++++------------------------ 2 files changed, 17 insertions(+), 56 deletions(-) diff --git a/app/api/entities/project_entity.rb b/app/api/entities/project_entity.rb index 054e5bd1c5..d48609bfb4 100644 --- a/app/api/entities/project_entity.rb +++ b/app/api/entities/project_entity.rb @@ -23,6 +23,10 @@ class ProjectEntity < Grape::Entity expose :tasks, using: TaskEntity, unless: :summary_only do |project, options| project.task_details_for_shallow_serializer(options[:user]) end + # changed here + expose :task_definitions, using: TaskDefinitionEntity, if: :include_task_definitions do |project, options| + project.unit.task_definitions + end expose :tutorial_enrolments, using: TutorialEnrolmentEntity, unless: :summary_only expose :groups, using: GroupEntity, unless: :summary_only diff --git a/app/api/projects_api.rb b/app/api/projects_api.rb index db632511da..9e355d8a0e 100644 --- a/app/api/projects_api.rb +++ b/app/api/projects_api.rb @@ -12,68 +12,25 @@ class ProjectsApi < Grape::API desc "Fetches all of the current user's projects" params do optional :include_inactive, type: Boolean, desc: 'Include projects for units that are no longer active?' + optional :include_task_definitions, type: Boolean, desc: 'Include all task definitions for each project unit? Also exposes tasks.' end get '/projects' do include_inactive = params[:include_inactive] || false + include_task_definitions = params[:include_task_definitions] || false - projects = Project.eager_load(:unit, :user).for_user current_user, include_inactive - present projects, with: Entities::ProjectEntity, for_student: true, summary_only: true, user: current_user - end - - desc "Fetches all of the current user's projects with the tasks for crossunit dashboard" - get "/projects/crossunit" do - projects = Project.eager_load(:unit).for_user(current_user, false) - - result = projects.map do |project| - unit = project.unit - - # Index existing task records by their task_definition_id for quick lookup - existing_tasks = project.tasks.eager_load(:task_definition).index_by(&:task_definition_id) - - tasks = unit.task_definitions.map do |td| - task = existing_tasks[td.id] - { - id: task&.id, - task_definition_id: td.id, - abbreviation: td.abbreviation, - name: td.name, - description: td.description, - status: task ? TaskStatus.id_to_key(task.task_status_id) : 'not_started', - target_date: td.target_date, - due_date: td.due_date, - start_date: td.start_date, - submission_date: task&.submission_date, - completion_date: task&.completion_date, - target_grade: td.target_grade, - weighting: td.weighting, - upload_requirements: td.upload_requirements, - is_graded: td.is_graded, - max_quality_pts: td.max_quality_pts - } - end - - { - id: project.id, - campus_id: project.campus_id, - user_id: project.user_id, - target_grade: project.target_grade, - spec_con_days: project.spec_con_days, - portfolio_available: project.portfolio_available, - escalation_attempts_remaining: project.escalation_attempts_remaining, - unit: { - code: unit.code, - id: unit.id, - name: unit.name, - my_role: project.student == current_user ? 'Student' : 'Staff', - start_date: unit.start_date, - end_date: unit.end_date, - active: unit.active - }, - tasks: tasks - } + projects = if include_task_definitions + Project.eager_load(:unit, :user, :tasks, unit: :task_definitions).for_user(current_user, include_inactive) + else + Project.eager_load(:unit, :user).for_user(current_user, include_inactive) end - present result, with: Grape::Presenters::Presenter + present projects, + with: Entities::ProjectEntity, + for_student: true, + # Disable summary_only so tasks are exposed for request + summary_only: !include_task_definitions, + include_task_definitions: include_task_definitions, + user: current_user end desc 'Get project' From 6700680a66cfd5aac51592b3a54a4850a21dafe3 Mon Sep 17 00:00:00 2001 From: owens-hub-git Date: Mon, 11 May 2026 15:58:11 +1000 Subject: [PATCH 3/5] fix: change for styling how the thing asks for --- app/api/projects_api.rb | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/app/api/projects_api.rb b/app/api/projects_api.rb index 9e355d8a0e..0877777ccb 100644 --- a/app/api/projects_api.rb +++ b/app/api/projects_api.rb @@ -23,14 +23,8 @@ class ProjectsApi < Grape::API else Project.eager_load(:unit, :user).for_user(current_user, include_inactive) end - - present projects, - with: Entities::ProjectEntity, - for_student: true, - # Disable summary_only so tasks are exposed for request - summary_only: !include_task_definitions, - include_task_definitions: include_task_definitions, - user: current_user + # Disable summary_only so tasks are exposed for request + present projects, with: Entities::ProjectEntity, for_student: true, summary_only: !include_task_definitions, include_task_definitions: include_task_definitions, user: current_user end desc 'Get project' From f5828a9a61aad54c1831229db6d846f31d62096b Mon Sep 17 00:00:00 2001 From: owens-hub-git Date: Tue, 12 May 2026 22:59:20 +1000 Subject: [PATCH 4/5] fix: for review from Ly --- app/api/entities/minimal/minimal_unit_entity.rb | 1 + app/api/entities/project_entity.rb | 4 ---- app/api/projects_api.rb | 12 ++++-------- 3 files changed, 5 insertions(+), 12 deletions(-) diff --git a/app/api/entities/minimal/minimal_unit_entity.rb b/app/api/entities/minimal/minimal_unit_entity.rb index bbead2cd81..8218308cd7 100644 --- a/app/api/entities/minimal/minimal_unit_entity.rb +++ b/app/api/entities/minimal/minimal_unit_entity.rb @@ -20,6 +20,7 @@ class MinimalUnitEntity < Grape::Entity end expose :active + expose :task_definitions, if: :include_task_definitions end end end diff --git a/app/api/entities/project_entity.rb b/app/api/entities/project_entity.rb index d48609bfb4..054e5bd1c5 100644 --- a/app/api/entities/project_entity.rb +++ b/app/api/entities/project_entity.rb @@ -23,10 +23,6 @@ class ProjectEntity < Grape::Entity expose :tasks, using: TaskEntity, unless: :summary_only do |project, options| project.task_details_for_shallow_serializer(options[:user]) end - # changed here - expose :task_definitions, using: TaskDefinitionEntity, if: :include_task_definitions do |project, options| - project.unit.task_definitions - end expose :tutorial_enrolments, using: TutorialEnrolmentEntity, unless: :summary_only expose :groups, using: GroupEntity, unless: :summary_only diff --git a/app/api/projects_api.rb b/app/api/projects_api.rb index 0877777ccb..2693619f17 100644 --- a/app/api/projects_api.rb +++ b/app/api/projects_api.rb @@ -12,19 +12,15 @@ class ProjectsApi < Grape::API desc "Fetches all of the current user's projects" params do optional :include_inactive, type: Boolean, desc: 'Include projects for units that are no longer active?' - optional :include_task_definitions, type: Boolean, desc: 'Include all task definitions for each project unit? Also exposes tasks.' + optional :include_task_definitions, type: Boolean, desc: 'Include all task definitions with tasks for each project?' end get '/projects' do include_inactive = params[:include_inactive] || false include_task_definitions = params[:include_task_definitions] || false - projects = if include_task_definitions - Project.eager_load(:unit, :user, :tasks, unit: :task_definitions).for_user(current_user, include_inactive) - else - Project.eager_load(:unit, :user).for_user(current_user, include_inactive) - end - # Disable summary_only so tasks are exposed for request - present projects, with: Entities::ProjectEntity, for_student: true, summary_only: !include_task_definitions, include_task_definitions: include_task_definitions, user: current_user + projects = Project.eager_load(:unit, :user).for_user current_user, include_inactive + + present projects, with: Entities::ProjectEntity, for_student: true, summary_only: true, include_task_definitions: include_task_definitions, user: current_user end desc 'Get project' From 145221cddb0a156b20652c0b55e889f49674ab97 Mon Sep 17 00:00:00 2001 From: owens-hub-git Date: Tue, 12 May 2026 23:27:52 +1000 Subject: [PATCH 5/5] fix: again for Ly if both conditions --- app/api/entities/project_entity.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/api/entities/project_entity.rb b/app/api/entities/project_entity.rb index 054e5bd1c5..e50dac74ea 100644 --- a/app/api/entities/project_entity.rb +++ b/app/api/entities/project_entity.rb @@ -20,7 +20,7 @@ class ProjectEntity < Grape::Entity expose :task_stats, as: :stats, unless: :for_student - expose :tasks, using: TaskEntity, unless: :summary_only do |project, options| + expose :tasks, using: TaskEntity, if: ->(project, options) { !options[:summary_only] || options[:include_task_definitions] } do |project, options| project.task_details_for_shallow_serializer(options[:user]) end