From be41cebcca0a919c9116011d4aca0b7267b5cb48 Mon Sep 17 00:00:00 2001 From: Bernardo Anderson Date: Thu, 7 Aug 2025 17:00:35 -0500 Subject: [PATCH] CP-10359 - Add ATS prefill field extraction for template and submission views - Add PrefillFieldsHelper module to extract ATS field data from base64 encoded parameters - Integrate ATS field extraction into TemplatesController and SubmissionsController - Support employee, manager, account, and location field name patterns - Add comprehensive test coverage for field validation and error handling - Remove unused backgroundColor style from template builder --- app/controllers/submissions_controller.rb | 7 + app/controllers/templates_controller.rb | 5 + app/helpers/prefill_fields_helper.rb | 33 +++++ app/javascript/template_builder/builder.vue | 1 - spec/helpers/prefill_fields_helper_spec.rb | 153 ++++++++++++++++++++ 5 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 app/helpers/prefill_fields_helper.rb create mode 100644 spec/helpers/prefill_fields_helper_spec.rb diff --git a/app/controllers/submissions_controller.rb b/app/controllers/submissions_controller.rb index 389e9591..3b663bad 100644 --- a/app/controllers/submissions_controller.rb +++ b/app/controllers/submissions_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class SubmissionsController < ApplicationController + include PrefillFieldsHelper + skip_before_action :verify_authenticity_token before_action :load_template, only: %i[new create] authorize_resource :template, only: %i[new create] @@ -15,6 +17,10 @@ class SubmissionsController < ApplicationController def show @submission = Submissions.preload_with_pages(@submission) + @available_ats_fields = extract_ats_prefill_fields + + # Optional: store in session for persistence across requests + session[:ats_prefill_fields] = @available_ats_fields if @available_ats_fields.any? unless @submission.submitters.all?(&:completed_at?) ActiveRecord::Associations::Preloader.new( @@ -91,6 +97,7 @@ class SubmissionsController < ApplicationController private + def save_template_message(template, params) template.preferences['request_email_subject'] = params[:subject] if params[:subject].present? template.preferences['request_email_body'] = params[:body] if params[:body].present? diff --git a/app/controllers/templates_controller.rb b/app/controllers/templates_controller.rb index 2828ce58..2c77ed73 100644 --- a/app/controllers/templates_controller.rb +++ b/app/controllers/templates_controller.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class TemplatesController < ApplicationController + include PrefillFieldsHelper + skip_before_action :maybe_redirect_to_setup skip_before_action :verify_authenticity_token @@ -39,6 +41,9 @@ class TemplatesController < ApplicationController associations: [schema_documents: [:blob, { preview_images_attachments: :blob }]] ).call + # Process ATS fields for template editing + @available_ats_fields = extract_ats_prefill_fields + @template_data = @template.as_json.merge( documents: @template.schema_documents.as_json( diff --git a/app/helpers/prefill_fields_helper.rb b/app/helpers/prefill_fields_helper.rb new file mode 100644 index 00000000..f5ba22be --- /dev/null +++ b/app/helpers/prefill_fields_helper.rb @@ -0,0 +1,33 @@ +# frozen_string_literal: true + +module PrefillFieldsHelper + def extract_ats_prefill_fields + return [] if params[:ats_fields].blank? + + begin + decoded_json = Base64.urlsafe_decode64(params[:ats_fields]) + field_names = JSON.parse(decoded_json) + + # Validate that we got an array of strings + return [] unless field_names.is_a?(Array) && field_names.all?(String) + + # Filter to only expected field name patterns + valid_fields = field_names.select { |name| valid_ats_field_name?(name) } + + # Log successful field reception + Rails.logger.info "Received #{valid_fields.length} ATS prefill fields: #{valid_fields.join(', ')}" + + valid_fields + rescue StandardError => e + Rails.logger.warn "Failed to parse ATS prefill fields: #{e.message}" + [] + end + end + + private + + def valid_ats_field_name?(name) + # Only allow expected field name patterns (security) + name.match?(/\A(employee|manager|account|location)_[a-z_]+\z/) + end +end diff --git a/app/javascript/template_builder/builder.vue b/app/javascript/template_builder/builder.vue index d60d9e5d..b05f1d67 100644 --- a/app/javascript/template_builder/builder.vue +++ b/app/javascript/template_builder/builder.vue @@ -55,7 +55,6 @@ id="title_container" class="flex justify-between py-1.5 items-center pr-4 top-0 z-10 title-container" :class="{ sticky: withStickySubmitters || isBreakpointLg }" - :style="{ backgroundColor }" >
diff --git a/spec/helpers/prefill_fields_helper_spec.rb b/spec/helpers/prefill_fields_helper_spec.rb new file mode 100644 index 00000000..9cd00f9e --- /dev/null +++ b/spec/helpers/prefill_fields_helper_spec.rb @@ -0,0 +1,153 @@ +# frozen_string_literal: true + +require 'rails_helper' + +RSpec.describe PrefillFieldsHelper, type: :helper do + describe '#extract_ats_prefill_fields' do + it 'extracts valid field names from base64 encoded parameter' do + fields = %w[employee_first_name employee_email manager_firstname] + encoded = Base64.urlsafe_encode64(fields.to_json) + + allow(helper).to receive(:params).and_return({ ats_fields: encoded }) + + result = helper.extract_ats_prefill_fields + expect(result).to eq(fields) + end + + it 'returns empty array for invalid base64' do + allow(helper).to receive(:params).and_return({ ats_fields: 'invalid_base64' }) + + result = helper.extract_ats_prefill_fields + expect(result).to eq([]) + end + + it 'returns empty array for invalid JSON' do + invalid_json = Base64.urlsafe_encode64('invalid json') + allow(helper).to receive(:params).and_return({ ats_fields: invalid_json }) + + result = helper.extract_ats_prefill_fields + expect(result).to eq([]) + end + + it 'filters out invalid field names' do + fields = %w[employee_first_name malicious_field account_name invalid-field] + encoded = Base64.urlsafe_encode64(fields.to_json) + + allow(helper).to receive(:params).and_return({ ats_fields: encoded }) + + result = helper.extract_ats_prefill_fields + expect(result).to eq(%w[employee_first_name account_name]) + end + + it 'returns empty array when no ats_fields parameter' do + allow(helper).to receive(:params).and_return({}) + + result = helper.extract_ats_prefill_fields + expect(result).to eq([]) + end + + it 'returns empty array when ats_fields parameter is empty' do + allow(helper).to receive(:params).and_return({ ats_fields: '' }) + + result = helper.extract_ats_prefill_fields + expect(result).to eq([]) + end + + it 'returns empty array when decoded JSON is not an array' do + not_array = Base64.urlsafe_encode64({ field: 'employee_name' }.to_json) + allow(helper).to receive(:params).and_return({ ats_fields: not_array }) + + result = helper.extract_ats_prefill_fields + expect(result).to eq([]) + end + + it 'returns empty array when array contains non-string values' do + mixed_array = ['employee_first_name', 123, 'manager_firstname'] + encoded = Base64.urlsafe_encode64(mixed_array.to_json) + + allow(helper).to receive(:params).and_return({ ats_fields: encoded }) + + result = helper.extract_ats_prefill_fields + expect(result).to eq([]) + end + + it 'accepts all valid field name patterns' do + fields = %w[ + employee_first_name + employee_middle_name + employee_last_name + employee_email + manager_firstname + manager_lastname + account_name + location_name + location_street + ] + encoded = Base64.urlsafe_encode64(fields.to_json) + + allow(helper).to receive(:params).and_return({ ats_fields: encoded }) + + result = helper.extract_ats_prefill_fields + expect(result).to eq(fields) + end + + it 'logs successful field reception' do + fields = %w[employee_first_name employee_email] + encoded = Base64.urlsafe_encode64(fields.to_json) + + allow(helper).to receive(:params).and_return({ ats_fields: encoded }) + allow(Rails.logger).to receive(:info) + + helper.extract_ats_prefill_fields + + expect(Rails.logger).to have_received(:info).with( + 'Received 2 ATS prefill fields: employee_first_name, employee_email' + ) + end + + it 'logs parsing errors' do + allow(helper).to receive(:params).and_return({ ats_fields: 'invalid_base64' }) + allow(Rails.logger).to receive(:warn) + + helper.extract_ats_prefill_fields + + expect(Rails.logger).to have_received(:warn).with( + a_string_matching(/Failed to parse ATS prefill fields:/) + ) + end + end + + describe '#valid_ats_field_name?' do + it 'returns true for valid employee field names' do + expect(helper.send(:valid_ats_field_name?, 'employee_first_name')).to be true + expect(helper.send(:valid_ats_field_name?, 'employee_email')).to be true + expect(helper.send(:valid_ats_field_name?, 'employee_phone_number')).to be true + end + + it 'returns true for valid manager field names' do + expect(helper.send(:valid_ats_field_name?, 'manager_firstname')).to be true + expect(helper.send(:valid_ats_field_name?, 'manager_lastname')).to be true + expect(helper.send(:valid_ats_field_name?, 'manager_email')).to be true + end + + it 'returns true for valid account field names' do + expect(helper.send(:valid_ats_field_name?, 'account_name')).to be true + expect(helper.send(:valid_ats_field_name?, 'account_id')).to be true + end + + it 'returns true for valid location field names' do + expect(helper.send(:valid_ats_field_name?, 'location_name')).to be true + expect(helper.send(:valid_ats_field_name?, 'location_street')).to be true + expect(helper.send(:valid_ats_field_name?, 'location_city')).to be true + end + + it 'returns false for invalid field names' do + expect(helper.send(:valid_ats_field_name?, 'malicious_field')).to be false + expect(helper.send(:valid_ats_field_name?, 'invalid-field')).to be false + expect(helper.send(:valid_ats_field_name?, 'EMPLOYEE_NAME')).to be false + expect(helper.send(:valid_ats_field_name?, 'employee')).to be false + expect(helper.send(:valid_ats_field_name?, 'employee_')).to be false + expect(helper.send(:valid_ats_field_name?, '_employee_name')).to be false + end + end +end