Skip to content

Commit

Permalink
[CPDLP-3080] Add NPQ participant profiles endpoint (#1403)
Browse files Browse the repository at this point in the history
* [CPDLP-3080] Add NPQ participant profiles endpoint

* Fix flakey test

* [CPDLP-3080] Address PR comments

* [CPDLP-3080] Address PR comments
  • Loading branch information
leandroalemao authored Jun 11, 2024
1 parent b0261af commit 958b929
Show file tree
Hide file tree
Showing 15 changed files with 792 additions and 23 deletions.
24 changes: 23 additions & 1 deletion app/controllers/api/v1/participants_controller.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
module API
module V1
class ParticipantsController < BaseController
def index = head(:method_not_allowed)
include Pagination
include ::API::Concerns::FilterByUpdatedSince

def index
render json: to_json(paginate(participants_query.participants))
end

def show = head(:method_not_allowed)
def change_schedule = head(:method_not_allowed)
def defer = head(:method_not_allowed)
def withdraw = head(:method_not_allowed)
def resume = head(:method_not_allowed)
def outcomes = head(:method_not_allowed)

private

def participants_query
conditions = { lead_provider: current_lead_provider, updated_since: }

::Participants::Query.new(**conditions.compact)
end

def participant_params
params.permit(filter: %i[updated_since])
end

def to_json(obj)
ParticipantSerializer.render(obj, view: :v1, root: "data", lead_provider: current_lead_provider)
end
end
end
end
24 changes: 23 additions & 1 deletion app/controllers/api/v2/participants_controller.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,35 @@
module API
module V2
class ParticipantsController < BaseController
def index = head(:method_not_allowed)
include Pagination
include ::API::Concerns::FilterByUpdatedSince

def index
render json: to_json(paginate(participants_query.participants))
end

def show = head(:method_not_allowed)
def change_schedule = head(:method_not_allowed)
def defer = head(:method_not_allowed)
def withdraw = head(:method_not_allowed)
def resume = head(:method_not_allowed)
def outcomes = head(:method_not_allowed)

private

def participants_query
conditions = { lead_provider: current_lead_provider, updated_since: }

::Participants::Query.new(**conditions.compact)
end

def participant_params
params.permit(filter: %i[updated_since])
end

def to_json(obj)
ParticipantSerializer.render(obj, view: :v2, root: "data", lead_provider: current_lead_provider)
end
end
end
end
32 changes: 31 additions & 1 deletion app/controllers/api/v3/participants_controller.rb
Original file line number Diff line number Diff line change
@@ -1,13 +1,43 @@
module API
module V3
class ParticipantsController < BaseController
def index = head(:method_not_allowed)
include Pagination
include ::API::Concerns::FilterByUpdatedSince

def index
render json: to_json(paginate(participants_query.participants))
end

def show = head(:method_not_allowed)
def change_schedule = head(:method_not_allowed)
def defer = head(:method_not_allowed)
def withdraw = head(:method_not_allowed)
def resume = head(:method_not_allowed)
def outcomes = head(:method_not_allowed)

private

def training_status
participant_params.dig(:filter, :training_status)
end

def from_participant_id
participant_params.dig(:filter, :from_participant_id)
end

def participants_query
conditions = { lead_provider: current_lead_provider, updated_since:, training_status:, from_participant_id: }

::Participants::Query.new(**conditions.compact)
end

def participant_params
params.permit(:sort, filter: %i[updated_since training_status from_participant_id])
end

def to_json(obj)
ParticipantSerializer.render(obj, view: :v3, root: "data", lead_provider: current_lead_provider)
end
end
end
end
146 changes: 146 additions & 0 deletions app/serializers/api/participant_serializer.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
module API
class ParticipantSerializer < Blueprinter::Base
identifier :ecf_id, name: :id
field(:type) { "npq-participant" }

class AttributesSerializer < Blueprinter::Base
exclude :id

view :v1 do
field(:ecf_id, name: :participant_id)
field(:full_name)
field(:email)

field(:npq_courses) do |object, options|
applications(object, options).map { |application| application.course.identifier }
end

field(:funded_places) do |object, options|
applications(object, options).map do |application|
{
npq_course: application.course.identifier,
funded_place: application.funded_place,
npq_application_id: application.ecf_id,
}
end
end

field(:trn, name: :teacher_reference_number)
field(:updated_at)
end

view :v2 do
field(:email)
field(:full_name)
field(:trn, name: :teacher_reference_number)
field(:updated_at)

field(:npq_enrolments) do |object, options|
applications(object, options).map do |application|
{
course_identifier: application.course.identifier,
schedule_identifier: application&.schedule&.identifier,
cohort: application.cohort&.start_year&.to_s,
npq_application_id: application.ecf_id,
eligible_for_funding: application.eligible_for_funding,
training_status: application.training_status,
school_urn: application.school&.urn,
targeted_delivery_funding_eligibility: application.targeted_delivery_funding_eligibility,
funded_place: application.funded_place,
}
end
end
end

view :v3 do
field(:full_name)
field(:trn, name: :teacher_reference_number)
field(:updated_at)

field(:npq_enrolments) do |object, options|
applications(object, options).map do |application|
{
email: object.email,
course_identifier: application.course.identifier,
schedule_identifier: application&.schedule&.identifier,
cohort: application.cohort&.start_year&.to_s,
npq_application_id: application.ecf_id,
eligible_for_funding: application.eligible_for_funding,
training_status: application.training_status,
school_urn: application.school&.urn,
targeted_delivery_funding_eligibility: application.targeted_delivery_funding_eligibility,
withdrawal: withdrawal(application:, lead_provider: options[:lead_provider]),
deferral: deferral(application:, lead_provider: options[:lead_provider]),
created_at: application.created_at.rfc3339,
funded_place: application.funded_place,
}
end
end

field(:participant_id_changes) do |object, _options|
(object.participant_id_changes || []).map do |participant_id_change|
{
from_participant_id: participant_id_change.from_participant.ecf_id,
to_participant_id: participant_id_change.to_participant.ecf_id,
changed_at: participant_id_change.created_at.rfc3339,
}
end
end
end

class << self
def applications(object, options)
return Application.none unless options[:lead_provider]

object.applications.select { |application| application.lead_provider_id == options[:lead_provider].id }
end

def withdrawal(application:, lead_provider:)
if application.withdrawn?
# We are doing this in memory to avoid running those as queries on each request
latest_application_state = application
.application_states.sort_by(&:created_at)
.reverse!
.find { |as| as.state == ApplicationState.states[:withdrawn] && as.lead_provider_id == lead_provider.id }

if latest_application_state.present?
{
reason: latest_application_state.reason,
date: latest_application_state.created_at.rfc3339,
}
end
end
end

def deferral(application:, lead_provider:)
if application.deferred?
# We are doing this in memory to avoid running those as queries on each request
latest_application_state = application
.application_states.sort_by(&:created_at)
.reverse!
.find { |as| as.state == ApplicationState.states[:deferred] && as.lead_provider_id == lead_provider.id }

if latest_application_state.present?
{
reason: latest_application_state.reason,
date: latest_application_state.created_at.rfc3339,
}
end
end
end
end
end

association :attributes, blueprint: AttributesSerializer do |participant|
participant
end

%i[v1 v2 v3].each do |version|
view version do
association :attributes, blueprint: AttributesSerializer, view: version do |participant|
participant
end
end
end
end
end
67 changes: 67 additions & 0 deletions app/services/participants/query.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
module Participants
class Query
include API::Concerns::Orderable

attr_reader :scope, :sort

def initialize(lead_provider: :ignore, updated_since: :ignore, training_status: :ignore, from_participant_id: :ignore, sort: nil)
@scope = all_participants
@sort = sort

where_lead_provider_is(lead_provider)
where_updated_since(updated_since)
where_training_status_is(training_status)
where_from_participant_id_is(from_participant_id)
end

def participants
scope.order(order_by)
end

private

def where_lead_provider_is(lead_provider)
return if lead_provider == :ignore

scope.merge!(Application.where(lead_provider:))
end

def where_updated_since(updated_since)
return if updated_since == :ignore

scope.merge!(User.where(updated_at: updated_since..))
end

def where_training_status_is(training_status)
return unless Application.training_statuses[training_status]

scope.merge!(Application.where(training_status:))
end

def where_from_participant_id_is(from_participant_id)
return if from_participant_id == :ignore

scope.merge!(scope.joins(:participant_id_changes).merge(ParticipantIdChange.where(from_participant: User.where(ecf_id: from_participant_id))))
end

def order_by
sort_order(sort:, model: User, default: { created_at: :asc })
end

def all_participants
User
.joins(:applications).merge(Application.accepted)
.includes(
:participant_id_changes,
applications: %i[
course
school
cohort
lead_provider
application_states
schedule
],
)
end
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,7 @@ def create_participant(school:)
end

def accept_application(application)
# TODO: replace by Applications::Accept.new(application:).accept when service class is added
application.update!(lead_provider_approval_status: "accepted")
Applications::Accept.new(application:).accept
application.reload
end

Expand Down
4 changes: 2 additions & 2 deletions spec/factories/applications.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@

trait :accepted do
lead_provider_approval_status { :accepted }
schedule { Schedule.where(cohort:, course_group: course.course_group).sample || create(:schedule, cohort:) }
schedule { Schedule.where(cohort:, course_group: course.course_group).sample || create(:schedule, course_group: course.course_group, cohort:) }
end

trait :rejected do
Expand All @@ -64,8 +64,8 @@
end

trait :eligible_for_funded_place do
accepted
eligible_for_funding { true }
lead_provider_approval_status { :accepted }

after(:create) do |application|
application.cohort.update!(funding_cap: true)
Expand Down
1 change: 1 addition & 0 deletions spec/factories/schools.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
establishment_type_code do
%w[1 2 3 5 6 7 8 10 12 14 15 18 24 26 28 31 32 33 34 35 36 38 39 40 41 42 43 44 45 46].sample
end
eyl_funding_eligible { true }
end

trait :closed do
Expand Down
10 changes: 10 additions & 0 deletions spec/factories/users.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,15 @@
trait :with_verified_trn do
trn_verified { true }
end

trait :with_application do
transient do
lead_provider { LeadProvider.all.sample }
end

after(:create) do |participant, evaluator|
create(:application, :accepted, user: participant, lead_provider: evaluator.lead_provider)
end
end
end
end
Loading

0 comments on commit 958b929

Please sign in to comment.