-
-
Notifications
You must be signed in to change notification settings - Fork 499
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
[Issue 2899] Distribution by counties! #3328
Conversation
Hey @dorner. Would love your review, but even if it passes muster, let's not merge it until what's in the current main gets deployed to production (I don't want any QA issues with this to hold up the current release.) |
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.
Wow, what a meaty update! Super impressed at all the work! Let me know if you have any questions about any of the comments.
|
||
|
||
|
||
function calculate_client_share_total(){ |
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.
The "new" way of doing things is using Stimulus controllers. You can look in app/javascript
for some examples.
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. I've taken a look at stimulus, and it's not obvious to me how to convert this. Might be a thing to pair on, but I'm inclined to let the thing that is working work - maybe put in a technical debt item for someone to work on converting both this and the distribution edits, which will be similar.
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.
It has been converted.
|
||
function calculate_client_share_total(){ | ||
total = 0; | ||
console.log("*********mark 1 total is: " +total); |
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 should be console.debug
or removed entirely.
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.
Yes. Oversight. Will be corrected in next push.
function calculate_client_share_total(){ | ||
total = 0; | ||
console.log("*********mark 1 total is: " +total); | ||
const client_shares = document.querySelectorAll('*[id$="client_share"]') |
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.
Kind of annoying, but JavaScript variables and functions should be in camelCase instead of snake_case.
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.
Hm. Fine. Will be corrected in next push.
document.getElementById("partners-served-areas-client-share-total-warning").style.visibility= 'hidden'; | ||
// document.getElementById("profile-update-button").style.visibility = 'visible'; | ||
// document.getElementById("profile-update-button-disabled").style.visibility = 'hidden'; | ||
// document.getElementById("partner-county-client-share-total-warning-footer").style.visibility= 'hidden'; |
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.
Please remove commented out code if it's not going to be used.
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.
Also removed in next push
def show | ||
setup_date_range_picker | ||
distributions = current_organization.distributions.includes(:partner).during(helpers.selected_range) | ||
breakdown_hash = {} |
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 method is too long to be in a controller. It should be in a service class. I'd also recommend not using a hash at all here, but creating a strongly typed Struct to store these values.
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.
Moving it to a service in the next push. Considering the advice re the struct. Will probably do that too, because my tests for the service revealed a whole lotta awkwardness.
lib/tasks/load_us_counties.rake
Outdated
namespace :db do | ||
desc "Load US Counties" | ||
task :load_us_counties => :environment do | ||
if County.find_by(name:"Abbeville County", region: " South Carolina").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.
This seems kind of arbitrary - should we just do if County.none?
?
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... A situation where you need US counties and there are already counties, but none of them are US is rather unlikely [I can imagine it, because this is open source, but it's a stretch]. In next push.
Gemfile
Outdated
@@ -35,6 +35,8 @@ gem "paper_trail" | |||
gem "rolify", "~> 6.0" | |||
# Enforces "safe" migrations. | |||
gem "strong_migrations", "1.4.1" | |||
# imports (used to set up counties) | |||
gem "activerecord-import" |
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.
Rails supports mass inserts now: https://blog.saeloun.com/2022/07/26/rails-6-insert-all.html
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.
Alrighty. That's the last thing I'm adding in the "low hanging fruit" push I'll be doing shortly.
describe "show" do | ||
it "returns http success" do | ||
get distributions_by_county_show_path(default_params) | ||
expect(response).to have_http_status(:success) |
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.
Should we have some additional tests besides just "it doesn't error"?
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 added a bunch in on the service in the next push. Will that do?
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.
Maybe just some check for a particular string, e.g. the county and distribution name?
end | ||
end | ||
|
||
context "handles time ranges properly (not fully written yet)" do |
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.
does this need to be fixed up?
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.
Just the context title. Next push.
With re the questions above:
|
Thanks, @dorner! I hope to address most of these this week, FYI. (though there is a lot, and it requires learning...) |
…bution_report # Conflicts: # db/schema.rb
I think I've caught everything except the stimulus controller -- which is not yielding easily. |
The tests are all passing on local. |
Alas, there is still a problem with this -- the deletion is not working properly. Putting it to a WIP state. |
…"removed" item zero, plus a little rubocop happiness
Ok. The problem with the removal of served areas has been solved -- I'm a little concerned with my solution, though. With the data-actions on delete_served_area_button in ui_helper.rb, can we be sure that the first click will complete before the second one starts, or are we going to have to go down the road of zeroing the value, and then somehow calling the enveloping controller. It seems to be working, but my sample set is small. |
I'm also getting a fair number of cases where these new tests are failing when I run the whole test suite. It seems to be related to Faker.Address.unique.community calls. They always pass when I "Rerun Failed Tests". Setting it aside for the nonce, as I have some meaty support stuff to work on, but will want to revisit. It is not always the same test. |
Per @awwaiid's vocal request, I've merged main in, and found one of the tests that sometimes fail as an example: the test is at ./spec/models/partners/served_area_spec.rb:23 and the error is |
This just reinforces my dislike of using randomly generated data in tests. If there is a failure, you can't reliably reproduce it, and it makes writing the tests themselves a lot harder. |
… community. Bleah!
# end | ||
# end | ||
# end | ||
RSpec.describe DistributionsByCountyHelper, type: :helper do |
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.
Delete this file too if you delete the other
context "no served areas" do | ||
let(:profile) { FactoryBot.build(:partner_profile) } | ||
it "has 0 client share" do | ||
expect(profile.client_share_total).to eq(0) |
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 suggest a expect(profile.valid?).to eq(true)
here too, as below.
Co-authored-by: Brock Wilcox <awwaiid@thelackthereof.org>
Co-authored-by: Brock Wilcox <awwaiid@thelackthereof.org>
@cielf is this ready for re-review? Haven't looked at it in a while. |
@dorner Yes. I believe it is. |
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.
Everything looks good to me; I think getting it into user hands to see what feedback they have sounds good.
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.
Looking great! A few further suggestions, but they are on the minor side.
} | ||
calculateClientShareTotal(){ | ||
let total = 0; | ||
let share_targets = this.shareTargets |
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 line isn't really necessary since the next line can just be this.shareTargets.forEach(
this.warningTarget.style.color= 'red'; | ||
} | ||
return total; | ||
} |
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.
In general there are quite a lot of spacing fixes I'd put here (we don't have a JS linter):
- A space before and after =>
- A space before and after { and }
- A space before ( and after )
- A space before and after =
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.
Still missing some spaces before =
😄
errors.add(:base, "Total client share must be 0 or 100") | ||
end | ||
check | ||
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.
This can be tidied up:
def client_share_is_0_or_100
value = client_share_total
if value != 0 && value != 100
errors.add(:base, "Total client share must be 0 or 100")
end
end
|
||
class DistributionByCountyReportService | ||
def get_breakdown(distributions) | ||
breakdown_struct = Struct.new(:name, :region, :num_items, :amount) |
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'd have this as a constant above the method rather than defined in the method:
Breakdown = Struct.new(:name, :region, :num_items, :amount)
...
breakdowns['Unspecified'] = Breakdown.new("Unspecified", "ZZZ", 0, 0.00)
@return_value = @partner.valid? | ||
|
||
if @return_value | ||
@profile.served_areas.each(&:destroy!) |
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.
Can use destroy_all
here.
describe "show" do | ||
it "returns http success" do | ||
get distributions_by_county_show_path(default_params) | ||
expect(response).to have_http_status(:success) |
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.
Maybe just some check for a particular string, e.g. the county and distribution name?
partner_profile: @partner_1.profile, client_share: 20) | ||
@partner_2.profile.served_areas[0].county = @partner_1.profile.served_areas[0].county | ||
@partner_2.profile.served_areas[0].save | ||
@partner_2.reload |
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.
Is there a reason you're using instance variables here? Generally we prefer having data set up with let
blocks or in a before
block (if you don't need to reference them later).
|
||
it "shows 'Unspecified 100%' if no served_areas" do | ||
@distribution = create(:distribution, :with_items, item: item_1, organization: @user.organization) | ||
visit distributions_by_county_report_path |
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.
Can we move most of these from system to request specs? System specs are only really necessary if we're testing multiple pages (via clicks) or JavaScript. Simple visits should be doable with request specs, which don't fire up headless Chrome and hence are much faster.
end | ||
end | ||
|
||
def setup_overlapping_partners |
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.
Should this be in a shared file somewhere since it's used in multiple specs?
compressing summation Co-authored-by: Daniel Orner <daniel.orner@wishabi.com>
Hmmm.... that's odd -- it only did 1 check when I put the latest thing up -- I've not seen that before. Noting it to @dorner, and also requesting a re-review |
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.
Almost there! A couple more cleanup bits and I think we're ready!
} | ||
calculateClientShareTotal ( ){ | ||
let total = 0; | ||
this.share_targets.forEach( |
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.
Sorry - this won't work 😅 Stimulus requires a specific format for the target variables - it has to follow the JavaScript "CamelCase" format rather than the Ruby "snake_case" format. So this has to read this.shareTargets
.
this.warningTarget.style.color= 'red'; | ||
} | ||
return total; | ||
} |
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.
Still missing some spaces before =
😄
app/models/partners/profile.rb
Outdated
# business logic: the client share has to be 0 or 100 -- although it is an estimate only, making it 0 (not | ||
# specified at all) or 100 means we won't have people overallocating (> 100) and that they think about what | ||
# their allocation actually is | ||
if client_share_total != 0 && client_share_total != 100 |
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.
Can we add a variable here to act as a cache? Otherwise it'll sum the client shares twice instead of once.
it "returns http success" do | ||
get distributions_by_county_report_path(default_params) | ||
expect(response).to have_http_status(:success) | ||
expect(response.body).to include("Estimated Distributions by County for ") |
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.
Um - this doesn't have any actual data that's it checking for. Can we validate that we're at least viewing the page for the right distribution etc?
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.
Removing the plain jane test, as, if it would fail, the other tests will also fail.
context "basic behaviour with served areas" do | ||
it "handles multiple partners with overlapping service areas properly" do | ||
@distribution_p1 = create(:distribution, :with_items, item: item_1, organization: @user.organization, partner: partner_1, issued_at: issued_at_present) | ||
@distribution_p2 = create(:distribution, :with_items, item: item_1, organization: @user.organization, partner: partner_2, issued_at: issued_at_present) |
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.
Do these need to be instance variables? (Also line 30)
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.
LGTM! Let's fix the Gemfile.lock and this is good to merge for me.
Looks like we've got a real conflict here! We've taken somewhat different approaches, etc. |
Hey @dorner -- I think the test that is failing is actually in error -- that it should be checking for blankness rather than nilness. Shouldn't it? |
@cielf all looks good - now we have a conflict with schema.rb 😆 |
@dorner Yeah. I can handle that. Just use the most recent one. |
Holding my breath and pushing merge! (We don't have anything else that's big in the pipe for this release) |
Resolves #2899
Description
Ability to have show the distribution by counties. at a high level.
Addition of optional areas_served partial to partner profile. This allows partners to select the counties they serve, and assign a percentage to each.
Distribution by county 'report'. Note this is not currently exportable.
Button leading to distribution by county report added to dashboard
Import of US counties
NOTE: The Import of US counties job ( load_us_counties) will have to be run when this is promoted !!!!!!!
Some decisions I'm not 100% sure are the best:
1/ County has full name (e.g. Washington County, Vermont) and region (Vermont) only -- maybe it should have the short name (without region) also. But I think we could run a job to extract it in the future if we have need. Chose region rather than state, because we include US territories.
2/ I chose show, rather than index or something else for the action for distribution_by_county. It is an endpoint rather than a jumping off point, though I can imagine a world where we would want to have links on each county. Will show be confusing - i.e. will people think there is a corresponding ActiveRecord?
3/ Not 100% happy with the name "served_area" for the model. Service areas? Served_areas at least matches up with areas_served, which is the title of the partial. I thought about using client_share as the name, but served_area allows the possibility of having other things about the area in the future better.
I do expect we will be asked to expand this in the future, to show (for example) diapers by county. But let's see what happens -- that should not require more input.
Dependency:
gem "activerecord-import"
And it does use js, including select2.
Type of change
How Has This Been Tested?
New automatic tests
manual testing as I went along
Note that you will have to either bin/setup or just run the load_us_counties rake job in order to manually test .