Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
36f5852
Fix application_question.locator for payload
patanj101 Aug 8, 2024
f46f3f6
Fix application_question.locator for payload (remove byebug)
patanj101 Aug 8, 2024
3c4660f
created apply_to_job_spec
daniel-sussman Aug 8, 2024
33aa52c
Minor fixes for Bamboo
patanj101 Aug 8, 2024
31268ac
Workable: improvements & possibility to attach photo
patanj101 Aug 8, 2024
74cdbfe
Update question.option_text_values
patanj101 Aug 8, 2024
faef4c6
FieldsFormatter: DRY & Refactor attribute methods
patanj101 Aug 8, 2024
57d3224
FieldsFormatter: DRY & Refactor attribute methods (2)
patanj101 Aug 8, 2024
39f7cdc
Remove byebug
patanj101 Aug 8, 2024
8710538
apply_with_cheddar: fix
patanj101 Aug 8, 2024
2a034ff
Reset seeds.rb
patanj101 Aug 8, 2024
6dc9e26
rake task is functional except for date fields
daniel-sussman Aug 9, 2024
aa402f4
merged to branch
daniel-sussman Aug 9, 2024
616bc36
Formatter Ouput jsons update
patanj101 Aug 9, 2024
3c0e8ca
Formatter Ouput jsons delete
patanj101 Aug 9, 2024
0b83b89
Payload formatter to manage textarea and date format
patanj101 Aug 9, 2024
6f78152
updates to rake task
daniel-sussman Aug 9, 2024
0311e4e
merged branch
daniel-sussman Aug 9, 2024
a5ed9f5
Denied apply_with_cheddar for jobs with group type questions
patanj101 Aug 9, 2024
bbb0591
Prioritize Cover letter text over cover letter pdf
patanj101 Aug 9, 2024
e2c8167
Prioritize Cover letter text over cover letter pdf
patanj101 Aug 9, 2024
55f92e7
fix to ashby_form_filler
daniel-sussman Aug 9, 2024
eb5cbc0
merged to branch
daniel-sussman Aug 9, 2024
0b012bb
added full list of FormFillers to ApplyToJob
daniel-sussman Aug 9, 2024
33f16ec
Update converted_type for interaction purpose
patanj101 Aug 9, 2024
32de85d
Step back from formatted_answered_value
patanj101 Aug 9, 2024
5083aa1
Improve value output for payload
patanj101 Aug 9, 2024
86404b3
ApplicationSubmission: Fix ApplyJob call
patanj101 Aug 9, 2024
bc0d676
fixed apply_url with old_format greenhouse jobs
daniel-sussman Aug 12, 2024
2e37eda
fixed it properly this time
daniel-sussman Aug 12, 2024
673c2d4
tests:end_to_end is running correctly in the test environment
daniel-sussman Aug 12, 2024
37cba6d
bugfix to true_up_spec
daniel-sussman Aug 13, 2024
34bc584
added some rake_helpers
daniel-sussman Aug 13, 2024
a24aa55
apply_to_job_spec mocks a user and user_detail
daniel-sussman Aug 13, 2024
4ff06f3
Bamboo is passing end_to_end tests
daniel-sussman Aug 13, 2024
8121f8d
disabled attach_resume in FactoryBot
daniel-sussman Aug 13, 2024
1944139
Bamboo yes_no question -> :radiogroup
daniel-sussman Aug 14, 2024
52ca4e5
re-activated FormFiller submit
daniel-sussman Aug 14, 2024
b2ae2e2
end_to_end raketask can handle more standard fields
daniel-sussman Aug 15, 2024
f43183a
WorkableFormFiller is working for most questions but not group type
daniel-sussman Aug 15, 2024
ba49e50
group questions managed up to stimulus controller
daniel-sussman Aug 16, 2024
864d082
problems with simple_form, group questions will break at template page
daniel-sussman Aug 16, 2024
755a674
very hacky version of group question is working for Workable
daniel-sussman Aug 17, 2024
0eebbc3
fixed Workable issue with group fields having same id as non-group fi…
daniel-sussman Aug 17, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/assets/builds/tailwind.css

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion app/controllers/job_applications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def load_job_application

def job_application_params
params.require(:job_application)
.permit(:application_process_id, :id, :resume, :cover_letter, { additional_info: {} })
.permit(:application_process_id, :id, :cover_letter, :photo, :resume, { additional_info: {} })
.reject { |_key, value| value.blank? }
end

Expand Down
3 changes: 2 additions & 1 deletion app/helpers/application_processes_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,8 @@ def filled_value(question, job_application)
def previously_answered_value(question, last_applicant_answers)
return previously_answered_linkedin_value(last_applicant_answers) if question.linkedin_related?

last_applicant_answers.find { |hash| hash[question.attribute] }&.values&.first
value = last_applicant_answers.find { |hash| hash[question.attribute] }&.values&.first
value.is_a?(Hash) ? value.values.last : value # Daniel's edit
end

def previously_answered_linkedin_value(last_applicant_answers)
Expand Down
62 changes: 62 additions & 0 deletions app/helpers/rake_helpers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
module RakeHelpers
# presents a menu of the ATSs that we can seed and test end-to-end
# user's selection is parsed by parse_response
def prompt_for_ats
ats_list = {
AshbyHQ: true,
BambooHR: true,
Greenhouse: true,
Workable: true
}

user_ready = false
options_count = ats_list.count

until user_ready
display_options(ats_list)
response = fetch_input(options_count)
user_ready, ats_list = parse_response(ats_list, response)
end

ats_list.select { |_, value| value.present? }.keys # return an array of the user-selected values
end

private

def display_options(ats_list)
system('clear') || system('cls') # depending on mac/linux
puts "Select which Applicant Tracking Systems you'd like to test:\n"
ats_list.each_with_index do |(name, value), index|
spacing = ' ' * [1, 14 - name.length].max
puts " #{index + 1}) - #{name}#{spacing}[#{value ? 'x' : ' '}]"
end
end

def fetch_input(options_count)
puts "\nSelect a number between 1 and #{options_count}:"
puts "Enter 'x' to select/deselect all.\n"
puts "Enter 'c' to commit these options."
$stdin.gets.chomp.downcase
end

def parse_response(ats_list, response)
return [true, ats_list] if response == 'c' # user_ready = true
return select_deselect_all(ats_list) if response == 'x'

response = response.to_i
ats_list = select_deselect_option(ats_list, response) if response.positive? && response <= ats_list.count
[false, ats_list]
end

def select_deselect_all(ats_list)
ats_list = ats_list.values.any?(&:blank?) ? ats_list.transform_values { true } : ats_list.transform_values { false }
[false, ats_list]
end

def select_deselect_option(ats_list, response)
index = response - 1
key = ats_list.keys[index]
ats_list[key] ^= true # invert the value
ats_list
end
end
29 changes: 29 additions & 0 deletions app/javascript/controllers/group_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { Controller } from "@hotwired/stimulus";

// Adds groups to forms and adjusts the id and name attributes of each group element
// This makes it possible to distinguish between duplicated groups
export default class extends Controller {
static targets = ['group', 'template', 'placeholder'];

increment_ids(fields, labels, nextId) {
fields.forEach(field => {
const id = field.id
const name = field.name
field.id = id + `[${nextId}]`
field.name = name + `[${nextId}]`
})
labels.forEach(label => {
const forId = label.htmlFor
label.htmlFor = forId + `[${nextId}]`
})
}

addGroup() {
const newGroup = this.templateTarget.content.cloneNode(true)
const labels = newGroup.querySelectorAll('label')
const fields = newGroup.querySelectorAll('input, date')
const nextId = this.groupTarget.querySelectorAll('.question-group').length
this.increment_ids(fields, labels, nextId)
this.placeholderTarget.appendChild(newGroup)
}
}
4 changes: 3 additions & 1 deletion app/jobs/applier/apply_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ class ApplyJob < ApplicationJob
sidekiq_options retry: false

def perform(job_application)
Applier::ApplyToJob.call(job_application) if job_application.status == 'submitted'
# NB: There isn't enough time for job_application.status to update from 'completed' to 'submitted' when running the job inline in test environment
Applier::ApplyToJob.call(job_application)
# Applier::ApplyToJob.call(job_application) if job_application.status == 'submitted'
end
end
end
59 changes: 48 additions & 11 deletions app/models/application_question.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

class ApplicationQuestion
# == PORO - Accessors =====================================================
attr_accessor :section, :attribute, :label, :fields, :required, :description
attr_accessor :section, :attribute, :label, :fields, :group_id, :required, :description, :sub_questions

# == Attributes ===========================================================
# == Callbacks ============================================================
Expand All @@ -24,33 +24,63 @@ def initialize(questions_params)
# nb: attribute can be nil, so safe navigation operator is necessary with #include?

def agreement_checkbox? = type.eql?("agreement_checkbox")
def attachment? = photo? || resume?
def boolean? = type.eql?("boolean")
def checkbox? = type.eql?("checkbox")
def cover_letter? = attribute&.include?("cover_letter")
def date_picker? = type.eql?("date_picker")
# Daniel's edit below:
def group? = type.eql?("group")
def input? = type.eql?("input") || type.eql?("education_input")
def linkedin_related? = attribute&.include?('linkedin')
def multi_select? = type.eql?("multi_select")
def photo? = attribute.eql?("photo")
def radiogroup? = type.eql?("radiogroup")
def resume? = attribute.eql?("resume")
def select? = type.eql?("select") || type.eql?("education_select")
# Daniel's edit below:
def sub_question? = group_id.present?
def textarea? = type.eql?("textarea")
def upload? = type.eql?("upload")

def answered_value(job_application)
def boolean_options
[['Yes', 'true'], ['No', 'false']]
end

def converted_type
return 'input' if textarea?
return 'boolean' if agreement_checkbox?

type
end

def cover_letter_text_available?
attribute.eql?('cover_letter') && fields.count > 1 && fields.any? { |field| field['name'].eql?('cover_letter_text') }
end

def field
return fields.find { |field| field['name'].eql?('cover_letter_text') } if cover_letter_text_available?

fields.first
end

def answered_value(job_application, key = nil)
return job_application.resume.blob.url if resume? && job_application.resume.attached?
return job_application.cover_letter.blob.url if cover_letter? && job_application.cover_letter.attached?

job_application.additional_info[attribute]
end
value = key.present? ? job_application.additional_info[attribute][key] : job_application.additional_info[attribute] # Daniel's edit
return value.values.last if value.is_a?(Hash) # Daniel's edit

def boolean_options
[['Yes', 'true'], ['No', 'false']]
value = value.reject(&:blank?) if value.is_a?(Array)
value.is_a?(Array) && value.count.eql?(1) ? value.first : value
end

def field = fields.first
# Daniel's edit
def fetch_sub_questions
sub_questions.map { |question| ApplicationQuestion.new(question.merge(section:)) }
end

def locator = field['selector'] || "##{field['id']}"
def locator = field['selector'] || field['name']

def multi_checkbox?
type.eql?("checkbox") && (options&.count&.> 1)
Expand All @@ -59,15 +89,22 @@ def multi_checkbox?
def option_text_values(values)
return values if options.none?

options.to_h.invert.slice(*values).values
options.to_h.select { |_k, v| values.include?(v.to_s) }.keys
end

def options
field['options'].map { |option| [option['label'], option['value']] }
end

def payload(job_application)
{ locator:, interaction: type, value: answered_value(job_application) }
def payload(job_application, key = nil)
{ locator:, interaction: converted_type, value: payload_value(job_application, key) }
end

# Daniel's edit
def payload_value(job_application, key)
return answered_value(job_application, key) unless group?

fetch_sub_questions.map { |question| question.payload(job_application, key) }
end

def selector = field['selector'] || field['name'].to_s
Expand Down
2 changes: 1 addition & 1 deletion app/models/application_question_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ApplicationQuestionSet < ApplicationRecord
# == Instance Methods =====================================================
# == Class Methods ========================================================
def question_by_attribute(attribute)
questions.find { |question| question.attribute.eql?(attribute) }
questions.find { |question| question.attribute.eql?(attribute) || question.fetch_sub_questions.find { |question| question.attribute.eql?(attribute) } } # Daniel's edit
end

def questions
Expand Down
14 changes: 13 additions & 1 deletion app/models/concerns/ats/greenhouse/job_details.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ def job_url_api(base_url, company_id, job_id)

def job_details(_job, data)
title, location = fetch_title_and_location(data)
posting_url, apply_url = fetch_job_urls(data)
{
posting_url: data['absolute_url'],
posting_url:,
apply_url:,
title:,
description: Flipper.enabled?(:job_description) ? CGI.unescapeHTML(data['content']) : 'Not added yet',
non_geocoded_location_string: location,
Expand All @@ -40,6 +42,12 @@ def job_details(_job, data)

private

def fetch_job_urls(data)
posting_url = data['absolute_url']
apply_url = "#{posting_url}#app" unless new_format?(posting_url)
[posting_url, apply_url]
end

def fetch_employment_type(data)
default = 'Full-time'

Expand All @@ -57,6 +65,10 @@ def fetch_salary(data)
symbol = CURRENCY_CONVERTER[currency&.downcase]&.first
salary_low == salary_high ? "#{symbol}#{salary_low} #{currency}" : "#{symbol}#{salary_low} - #{symbol}#{salary_high} #{currency}"
end

def new_format?(url)
url.include?('job-boards')
end
end
end
end
2 changes: 1 addition & 1 deletion app/models/concerns/check_url_is_valid.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def fetch_redirect(url, response)
uri = URI.parse(url)
domain = uri.host
path = response['location']
"#{domain}/#{path}"
domain + path
end

def get_response(url, max_retries = 1)
Expand Down
15 changes: 13 additions & 2 deletions app/models/job_application.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class JobApplication < ApplicationRecord
belongs_to :job

has_one_attached :cover_letter
has_one_attached :photo
has_one_attached :resume
has_one :application_question_set, through: :job
has_one :applicant_tracking_system, through: :job
Expand All @@ -29,12 +30,22 @@ class JobApplication < ApplicationRecord
enum :status, { initial: "initial", completed: "completed", uncompleted: "uncompleted", submitted: "submitted", rejected: "rejected" },
default: :initial, validate: true

def attachment(question)
return photo if question.photo?
return cover_letter if question.cover_letter?

resume
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How come you've set it up like this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An application form has several attachments now ... and the number will probably grow.
I need to be able to retrieve that specific attachment.
I need the question as a parameter to be able to do so.

end

def payload
apply_url = job.apply_url || job.posting_url
user_fullname = application_process.user.user_detail.full_name
# Daniel's edit below: messy but it works
fields = application_question_set.questions.map do |question|
question.payload(self)
end
next unless question.type && (additional_info.keys.include?(question.attribute) || question.attachment?) # Why aren't attachments part of additional_info?

question.group? ? additional_info[question.attribute].map { |key, _| question.payload(self, key) } : question.payload(self)
end.compact.flatten
{ user_fullname:, apply_url:, fields: }
end

Expand Down
4 changes: 3 additions & 1 deletion app/services/applier/apply_to_job.rb
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ def form_filler
{
'Greenhouse' => check_which_greenhouse,
'AshbyHQ' => AshbyFormFiller,
'DevITJobs' => DevitFormFiller
'BambooHR' => BambooFormFiller,
'DevITJobs' => DevitFormFiller,
'Workable' => WorkableFormFiller
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to work out how best to route this in due course

}
end

Expand Down
2 changes: 2 additions & 0 deletions app/services/applier/ashby_form_filler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ def boolean_string
@value ? 'Yes' : 'No'
end

def click_apply_button = false

def handle_boolean
hidden_element
.sibling('button', text: boolean_string)
Expand Down
30 changes: 29 additions & 1 deletion app/services/applier/bamboo_form_filler.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,35 @@ class BambooFormFiller < FormFiller
def application_form = '#careerApplicationForm'

def attach_file_to_application
find("input[name='#{@locator}']")
find("input[name='#{@locator}']", visible: false)
.sibling('div')
.find('input')
.attach_file(@filepath)
sleep 2
end

def boolean_field = find(:css, "label[for='#{@locator}']")

def click_submit_button
sleep 12 # temporary -- just for testing
submit_button.click
end

# country must be filled in first
def fill_in_all_fields
prioritize_country_question
super
end

def handle_date_picker
convert_date
handle_input
end

def handle_radiogroup
find("label[for='#{@locator}--#{@value.downcase}']").click
end

def handle_select
@hidden_select_field = find("select[name='#{@locator}']")
return if option_prefilled?
Expand All @@ -24,6 +45,13 @@ def handle_select

def option_prefilled? = @hidden_select_field.has_css?("option[value='#{@value}']")

def prioritize_country_question
target_index = @fields.index { |field| field[:locator] == 'countryId' }
return unless target_index

@fields.insert(0, @fields.delete_at(target_index))
end

def select_menu = @hidden_select_field.sibling('div')

def select_option = page.document.find_by_id(@value, visible: true)
Expand Down
Loading