-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
FFS-2340: Encapsulate Events Handling in a New Model #491
base: main
Are you sure you want to change the base?
Conversation
@@ -11,9 +11,12 @@ def self.sti_class_for(type_name) | |||
|
|||
belongs_to :cbv_flow | |||
|
|||
after_update_commit { | |||
I18n.with_locale(cbv_flow.cbv_flow_invitation.language) do | |||
broadcast_replace target: self, partial: "cbv/synchronizations/indicators", locals: { pinwheel_account: self } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved this from the model -> webhooks controller, since it made a bit more sense to me to put it there.
We currently use Pinwheel-specific columns on `PayrollAccount` to track the status of the sync: income/paystubs/employment/identity synced_at/errored_at. To accommodate other payroll aggregators, and better record sync statuses, let's create a WebhookEvents model to record which webhooks we've received for a given PayrollAccount. Success or failure, then, is not determined by the columns, but by the presence of the necessary WebhookEvents for that payroll aggregator.
566fbe6
to
2b7ba7b
Compare
Rails.logger.info "Unable to find CbvFlow for end_user_id: #{params["payload"]["end_user_id"]}" | ||
return render json: { status: "ok" } | ||
@payroll_account = @cbv_flow.payroll_accounts.find_or_create_by(type: :pinwheel, pinwheel_account_id: params["payload"]["account_id"]) do |new_payroll_account| | ||
new_payroll_account.supported_jobs = get_supported_jobs(params["payload"]["platform_id"]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes a rare bug when the webhooks are received out-of-order, previously it failed because the account hadn't been created yet. Now, whichever webhook is received first will create the PayrollAccount.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if this is the bug that I encountered when pairing with @daphnegold. The synchronizations page would immediately fail for some reason unknown to me. Let's see if this fixes that issue.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was just pairing with her - hers was a setup issue (we have a limit of 10 pinwheel webhook subscriptions, and periodically need to garbage collect old engineers out of there). So her sync was failing because pinwheel never registered the webhooks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good works. Seems like a lot. I really dig the updates to the test suite.
app/app/models/payroll_account.rb
Outdated
broadcast_replace target: self, partial: "cbv/synchronizations/indicators", locals: { pinwheel_account: self } | ||
private | ||
|
||
def webhook_event(event_name, event_outcome = nil) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was a bit confused on the purpose of this method at first, but after reviewing some other logic in the code I can see that the purpose of this method is to check for the presence of a WebhookEvent
matching the name and status/outcome. Maybe we can consider adding a verb to this method? i.e. find_webhook_by_status
. It's a big verbose, but it makes following the flow a bit easier.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah that's a good idea. I was waffling on it - brevity vs being explicit. I almost always lean towards "being explicit" and I think you're right that I should have here as well. I'll go with find_webhook_event
.
(supported_jobs.exclude?("paystubs") || webhook_event(JOBS_TO_WEBHOOK_EVENTS["paystubs"]).present?) && | ||
(supported_jobs.exclude?("employment") || webhook_event(JOBS_TO_WEBHOOK_EVENTS["employment"]).present?) && | ||
(supported_jobs.exclude?("income") || webhook_event(JOBS_TO_WEBHOOK_EVENTS["income"]).present?) && | ||
(supported_jobs.exclude?("identity") || webhook_event(JOBS_TO_WEBHOOK_EVENTS["identity"]).present?) | ||
end | ||
|
||
def job_succeeded?(job) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice, this looks a lot better than the previous iteration.
let(:income_errored_at) { nil } | ||
let(:paystubs_errored_at) { nil } | ||
let(:employment_errored_at) { nil } | ||
let(:errored_jobs) { [] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice- I like this pattern more than explicitly digging up different *_errored_at
vars
transient do | ||
# Use this to create a test case where a job has failed to sync for a | ||
# PayrollAccount. | ||
with_errored_jobs { %w[] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some reason this is my favorite part about the PR. I appreciate a nice test suite.
end | ||
|
||
context "when job is unsupported" do | ||
let(:supported_jobs) { super() - [ "income" ] } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's going on here with super()
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good question. It refers to the supported_jobs
in the enclosing context/describe. Here's an example:
describe "how super() works" do
let(:arguments) { [1, 2] }
it { expect(arguments).to eq([1, 2]) } # passes
context "when there are three arguments" do
let(:arguments) { super() + [3] }
it { expect(arguments).to eq([1, 2, 3]) } # passes
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I try not to over-use it, because lord knows Rspec tests are already hard enough to understand where the variables are coming from even without this. But I did like how readable it is to do super() - [ "income" ]
to denote the fact that the "income" job is not supported.
Ticket
Resolves FFS-2340.
Changes
FFS-2340: Create
WebhookEvents
to track sync success/failureContext for reviewers
See above.
Acceptance testing
:alert: Deploy block! @ffs-eng I just merged PR [#123] and will be doing acceptance testing in demo - please don't deploy until I'm finished!
)