From 72aa2b4ef30bd9073be01fdee3975d36d29623e9 Mon Sep 17 00:00:00 2001 From: fumimowdan Date: Mon, 2 Oct 2023 13:45:47 +0100 Subject: [PATCH] Add FormsFunnelQuery This will generate the data used for the eligibel and ineligible forms funnels in the system admin dashboard --- app/queries/forms_funnel_query.rb | 131 ++++++++++++++++++++++++ spec/queries/forms_funnel_query_spec.rb | 86 ++++++++++++++++ 2 files changed, 217 insertions(+) create mode 100644 app/queries/forms_funnel_query.rb create mode 100644 spec/queries/forms_funnel_query_spec.rb diff --git a/app/queries/forms_funnel_query.rb b/app/queries/forms_funnel_query.rb new file mode 100644 index 00000000..cd52c211 --- /dev/null +++ b/app/queries/forms_funnel_query.rb @@ -0,0 +1,131 @@ +class FormsFunnelQuery + def self.call(...) + new(...).execute + end + + def initialize(date_range: 24.hours.ago..Time.current) + @date_range = date_range + @entity_class = "Form" + @base_query = Event + .select(:entity_id) + .distinct + .where(entity_class: entity_class, created_at: date_range) + end + + attr_reader :date_range, :entity_class, :base_query + + def execute + data = StepFlow::STEPS.each_with_object({}) do |(route_key, step), hsh| + hsh[route_key] = query_dispatch(step) + hsh + end + data["submission"] = submission_query + + data + end + + def application_route_step_query(required_field) + other_query(required_field) + end + + def school_details_step_query(required_field) + boolean_query(required_field) + end + + def trainee_details_step_query(required_field) + boolean_query(required_field) + end + + def contract_details_step_query(required_field) + boolean_query(required_field) + end + + def start_date_step_query(required_field) + grouping = extract_dates(required_field) + .group_by do |(_id, start_date)| + Form::EligibilityCheck.new(Form.new).contract_start_date_eligible?(start_date.to_date) + end + + count_grouping(grouping) + end + + def subject_step_query(required_field) + other_query(required_field) + end + + def visa_step_query(required_field) + other_query(required_field) + end + + def entry_date_step_query(required_field) + date_of_entries = extract_dates(required_field) + start_dates = extract_dates("start_date") + + form_dates = date_of_entries.each_with_object([]) do |(id, date_of_entry), list| + entry = start_dates.detect { |(sid, _)| sid == id } + list << [date_of_entry.to_date, entry.last.to_date] + list + end + + grouping = form_dates.group_by do |(date_of_entry, start_date)| + Form::EligibilityCheck.new(Form.new).date_of_entry_eligible?(date_of_entry, start_date) + end + + count_grouping(grouping) + end + + def personal_details_step_query(required_field) + submitted_field_query(required_field) + end + + def employment_details_step_query(required_field) + submitted_field_query(required_field) + end + + def submission_query + { eligible: base_query.where(action: :deleted).count } + end + +private + + def count_grouping(grouping) + { eligible: grouping.fetch(true, []).count, ineligible: grouping.fetch(false, []).count } + end + + def extract_dates(field) + base_query + .where("data->'#{field}' IS NOT NULL") + .pluck(:entity_id, Arel.sql("data->'#{field}'->1")) + end + + def submitted_field_query(field) + eligible = base_query.where("data->'#{field}' IS NOT NULL").count + + { eligible: } + end + + def boolean_query(field) + ineligible = base_query.where("data->'#{field}' @> ?", "[false]").count + eligible = base_query.where("data->'#{field}' @> ?", "[true]").count + + { eligible:, ineligible: } + end + + def other_query(field) + total = base_query.where("data->'#{field}' IS NOT NULL").count + ineligible = base_query.where("data->'#{field}' @> ?", '["other"]') + .or(base_query.where("data->'#{field}' @> ?", '["Other"]')) + .count + eligible = total - ineligible + + { eligible:, ineligible: } + end + + def query_dispatch(step) + method_name = "#{step.name.underscore}_query".to_sym + required_field = step::REQUIRED_FIELDS.first + return public_send(method_name, required_field) if respond_to?(method_name) + + raise(NoMethodError, "You must define query method #{method_name}") + end +end diff --git a/spec/queries/forms_funnel_query_spec.rb b/spec/queries/forms_funnel_query_spec.rb new file mode 100644 index 00000000..549be5f7 --- /dev/null +++ b/spec/queries/forms_funnel_query_spec.rb @@ -0,0 +1,86 @@ +require "rails_helper" + +RSpec.describe FormsFunnelQuery, type: :service do + subject(:forms_funnel) { described_class.new(date_range:) } + + let(:date_range) { 1.day.ago..Time.current } + + describe ".submission_query" do + before do + create_list(:form_submitted_event, 2) + end + + let(:expected) { { eligible: 2 } } + + it { expect(forms_funnel.submission_query).to eq(expected) } + end + + describe ".employment_details_step_query" do + before do + create_list(:form_employment_details_event, 3) + end + + let(:expected) { { eligible: 3 } } + + it { expect(forms_funnel.employment_details_step_query("school_name")).to eq(expected) } + end + + describe ".personal_details_step_query" do + before do + create_list(:form_personal_details_event, 4) + end + + let(:expected) { { eligible: 4 } } + + it { expect(forms_funnel.personal_details_step_query("given_name")).to eq(expected) } + end + + describe ".entry_date_step_query" do + before do + old_start_date = create(:form_start_date_four_months_ago_event) + create(:ineligible_form_date_of_entry_event, entity_id: old_start_date.entity_id) + start_dates = create_list(:form_start_date_event, 6) + build_list(:form_date_of_entry_event, 5) do |event, i| + event.entity_id = start_dates[i].entity_id + event.save + end + end + + let(:expected) { { eligible: 5, ineligible: 1 } } + + it { expect(forms_funnel.entry_date_step_query("date_of_entry")).to eq(expected) } + end + + describe ".start_date_step_query" do + before do + create_list(:form_start_date_event, 6) + create(:ineligible_form_start_date_event) + end + + let(:expected) { { eligible: 6, ineligible: 1 } } + + it { expect(forms_funnel.start_date_step_query("start_date")).to eq(expected) } + end + + describe ".application_route_step_query" do + before do + create_list(:form_application_route_event, 6) + create(:ineligible_form_application_route_event) + end + + let(:expected) { { eligible: 6, ineligible: 1 } } + + it { expect(forms_funnel.application_route_step_query("application_route")).to eq(expected) } + end + + describe ".school_details_step_query" do + before do + create_list(:form_school_details_event, 3) + create(:ineligible_form_school_details_event) + end + + let(:expected) { { eligible: 3, ineligible: 1 } } + + it { expect(forms_funnel.school_details_step_query("state_funded_secondary_school")).to eq(expected) } + end +end